@lvce-editor/extension-detail-view 5.14.0 → 6.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -100,7 +100,7 @@ const changelog = () => {
100
100
  const details = () => {
101
101
  return i18nString(Details$1);
102
102
  };
103
- const disable = () => {
103
+ const disable$1 = () => {
104
104
  return i18nString(Disable$1);
105
105
  };
106
106
  const features$1 = () => {
@@ -133,7 +133,7 @@ const status = () => {
133
133
  const setColorTheme$2 = () => {
134
134
  return i18nString(SetColorTheme$1);
135
135
  };
136
- const enable = () => {
136
+ const enable$1 = () => {
137
137
  return i18nString(Enable$1);
138
138
  };
139
139
  const theme = () => {
@@ -330,7 +330,8 @@ const ClientY = 'event.clientY';
330
330
  const TargetHref = 'event.target.href';
331
331
  const TargetName = 'event.target.name';
332
332
 
333
- const Script$1 = 2;
333
+ const LeftArrow = 13;
334
+ const RightArrow = 15;
334
335
 
335
336
  const ExtensionDetailReadme = 20;
336
337
  const ExtensionDetailIconContextMenu$3 = 4091;
@@ -339,10 +340,8 @@ const None$2 = 0;
339
340
 
340
341
  const Web$1 = 1;
341
342
 
342
- const ExtensionHostWorker = 44;
343
- const FileSystemWorker$1 = 209;
344
- const MarkdownWorker$1 = 300;
345
- const RendererWorker = 1;
343
+ const FocusElementByName = 'Viewlet.focusElementByName';
344
+ const SetFocusContext = 'Viewlet.setFocusContext';
346
345
 
347
346
  const mergeClassNames = (...classNames) => {
348
347
  return classNames.filter(Boolean).join(' ');
@@ -1030,8 +1029,16 @@ const getFeatureVirtualDomHandler = featureName => {
1030
1029
  return feature.getVirtualDom;
1031
1030
  };
1032
1031
 
1032
+ const Script$1 = 2;
1033
+
1034
+ const ExtensionHostWorker = 44;
1035
+ const ExtensionManagementWorker = 9006;
1036
+ const FileSystemWorker$1 = 209;
1037
+ const MarkdownWorker$1 = 300;
1038
+ const RendererWorker = 1;
1039
+
1033
1040
  const rpcs = Object.create(null);
1034
- const set$a = (id, rpc) => {
1041
+ const set$b = (id, rpc) => {
1035
1042
  rpcs[id] = rpc;
1036
1043
  };
1037
1044
  const get$3 = id => {
@@ -1040,6 +1047,10 @@ const get$3 = id => {
1040
1047
 
1041
1048
  const create$7 = rpcId => {
1042
1049
  return {
1050
+ async dispose() {
1051
+ const rpc = get$3(rpcId);
1052
+ await rpc.dispose();
1053
+ },
1043
1054
  // @ts-ignore
1044
1055
  invoke(method, ...params) {
1045
1056
  const rpc = get$3(rpcId);
@@ -1053,11 +1064,7 @@ const create$7 = rpcId => {
1053
1064
  return rpc.invokeAndTransfer(method, ...params);
1054
1065
  },
1055
1066
  set(rpc) {
1056
- set$a(rpcId, rpc);
1057
- },
1058
- async dispose() {
1059
- const rpc = get$3(rpcId);
1060
- await rpc.dispose();
1067
+ set$b(rpcId, rpc);
1061
1068
  }
1062
1069
  };
1063
1070
  };
@@ -1585,7 +1592,7 @@ const create$4 = (method, params) => {
1585
1592
  };
1586
1593
  };
1587
1594
  const callbacks = Object.create(null);
1588
- const set$9 = (id, fn) => {
1595
+ const set$a = (id, fn) => {
1589
1596
  callbacks[id] = fn;
1590
1597
  };
1591
1598
  const get$2 = id => {
@@ -1604,7 +1611,7 @@ const registerPromise = () => {
1604
1611
  resolve,
1605
1612
  promise
1606
1613
  } = Promise.withResolvers();
1607
- set$9(id, resolve);
1614
+ set$a(id, resolve);
1608
1615
  return {
1609
1616
  id,
1610
1617
  promise
@@ -1949,7 +1956,7 @@ const send = (transport, method, ...params) => {
1949
1956
  const message = create$4(method, params);
1950
1957
  transport.send(message);
1951
1958
  };
1952
- const invoke$5 = (ipc, method, ...params) => {
1959
+ const invoke$6 = (ipc, method, ...params) => {
1953
1960
  return invokeHelper(ipc, method, params, false);
1954
1961
  };
1955
1962
  const invokeAndTransfer$4 = (ipc, method, ...params) => {
@@ -1988,7 +1995,7 @@ const createRpc = ipc => {
1988
1995
  send(ipc, method, ...params);
1989
1996
  },
1990
1997
  invoke(method, ...params) {
1991
- return invoke$5(ipc, method, ...params);
1998
+ return invoke$6(ipc, method, ...params);
1992
1999
  },
1993
2000
  invokeAndTransfer(method, ...params) {
1994
2001
  return invokeAndTransfer$4(ipc, method, ...params);
@@ -2098,28 +2105,28 @@ const createMockRpc = ({
2098
2105
  };
2099
2106
 
2100
2107
  const {
2101
- invoke: invoke$4,
2108
+ dispose: dispose$4,
2109
+ invoke: invoke$5,
2102
2110
  invokeAndTransfer: invokeAndTransfer$3,
2103
- set: set$8,
2104
- dispose: dispose$4
2111
+ set: set$9
2105
2112
  } = create$7(ExtensionHostWorker);
2106
2113
  const executeReferenceProvider = async (id, offset) => {
2107
2114
  // @ts-ignore
2108
- return invoke$4('ExtensionHostReference.executeReferenceProvider', id, offset);
2115
+ return invoke$5('ExtensionHostReference.executeReferenceProvider', id, offset);
2109
2116
  };
2110
2117
  const executeFileReferenceProvider = async id => {
2111
2118
  // @ts-ignore
2112
- return invoke$4('ExtensionHostReference.executeFileReferenceProvider', id);
2119
+ return invoke$5('ExtensionHostReference.executeFileReferenceProvider', id);
2113
2120
  };
2114
2121
  const getRuntimeStatus$2 = async extensionId => {
2115
2122
  // @ts-ignore
2116
- return invoke$4('ExtensionHost.getRuntimeStatus', extensionId);
2123
+ return invoke$5('ExtensionHost.getRuntimeStatus', extensionId);
2117
2124
  };
2118
2125
  const registerMockRpc$2 = commandMap => {
2119
2126
  const mockRpc = createMockRpc({
2120
2127
  commandMap
2121
2128
  });
2122
- set$8(mockRpc);
2129
+ set$9(mockRpc);
2123
2130
  return mockRpc;
2124
2131
  };
2125
2132
 
@@ -2129,17 +2136,28 @@ const ExtensionHost = {
2129
2136
  executeFileReferenceProvider,
2130
2137
  executeReferenceProvider,
2131
2138
  getRuntimeStatus: getRuntimeStatus$2,
2132
- invoke: invoke$4,
2139
+ invoke: invoke$5,
2133
2140
  invokeAndTransfer: invokeAndTransfer$3,
2134
2141
  registerMockRpc: registerMockRpc$2,
2142
+ set: set$9
2143
+ };
2144
+
2145
+ const {
2146
+ invoke: invoke$4,
2135
2147
  set: set$8
2148
+ } = create$7(ExtensionManagementWorker);
2149
+ const enable = id => {
2150
+ return invoke$4(`Extensions.enable`, id);
2151
+ };
2152
+ const disable = id => {
2153
+ return invoke$4(`Extensions.disable`, id);
2136
2154
  };
2137
2155
 
2138
2156
  const {
2157
+ dispose: dispose$3,
2139
2158
  invoke: invoke$3,
2140
2159
  invokeAndTransfer: invokeAndTransfer$2,
2141
- set: set$7,
2142
- dispose: dispose$3
2160
+ set: set$7
2143
2161
  } = create$7(FileSystemWorker$1);
2144
2162
  const remove = async dirent => {
2145
2163
  return invoke$3('FileSystem.remove', dirent);
@@ -2224,10 +2242,10 @@ const FileSystemWorker = {
2224
2242
  };
2225
2243
 
2226
2244
  const {
2245
+ dispose: dispose$2,
2227
2246
  invoke: invoke$2,
2228
2247
  invokeAndTransfer: invokeAndTransfer$1,
2229
- set: set$6,
2230
- dispose: dispose$2
2248
+ set: set$6
2231
2249
  } = create$7(MarkdownWorker$1);
2232
2250
  const getVirtualDom$1 = async html => {
2233
2251
  // @ts-ignore
@@ -2259,7 +2277,8 @@ const MarkdownWorker = {
2259
2277
  const {
2260
2278
  invoke: invoke$1,
2261
2279
  invokeAndTransfer,
2262
- set: set$5} = create$7(RendererWorker);
2280
+ set: set$5
2281
+ } = create$7(RendererWorker);
2263
2282
  const showContextMenu2 = async (uid, menuId, x, y, args) => {
2264
2283
  number(uid);
2265
2284
  number(menuId);
@@ -2282,14 +2301,6 @@ const sendMessagePortToFileSystemWorker$1 = async (port, rpcId) => {
2282
2301
  // @ts-ignore
2283
2302
  await invokeAndTransfer('SendMessagePortToExtensionHostWorker.sendMessagePortToFileSystemWorker', port, command, rpcId);
2284
2303
  };
2285
- const disableExtension$1 = async id => {
2286
- // @ts-ignore
2287
- return invoke$1('ExtensionManagement.disable', id);
2288
- };
2289
- const enableExtension$1 = async id => {
2290
- // @ts-ignore
2291
- return invoke$1('ExtensionManagement.enable', id);
2292
- };
2293
2304
  const sendMessagePortToExtensionHostWorker$1 = async (port, rpcId = 0) => {
2294
2305
  const command = 'HandleMessagePort.handleMessagePort2';
2295
2306
  await invokeAndTransfer('SendMessagePortToExtensionHostWorker.sendMessagePortToExtensionHostWorker', port, command, rpcId);
@@ -2306,6 +2317,10 @@ const writeClipBoardImage = async blob => {
2306
2317
  // @ts-ignore
2307
2318
  await invoke$1('ClipBoard.writeImage', /* text */blob);
2308
2319
  };
2320
+ const sendMessagePortToExtensionManagementWorker = async (port, rpcId) => {
2321
+ const command = 'Extensions.handleMessagePort';
2322
+ await invokeAndTransfer('SendMessagePortToExtensionHostWorker.sendMessagePortToExtensionManagementWorker', port, command, rpcId);
2323
+ };
2309
2324
  const getAllExtensions$1 = async () => {
2310
2325
  return invoke$1('ExtensionManagement.getAllExtensions');
2311
2326
  };
@@ -2533,6 +2548,7 @@ const HandleTabsClick = 14;
2533
2548
  const HandleAdditionalDetailContextMenu = 15;
2534
2549
  const HandleReadmeClick = 16;
2535
2550
  const HandleSelectionChange = 17;
2551
+ const HandleTabFocus = 18;
2536
2552
 
2537
2553
  const ActivationEvents = 'ActivationEvents';
2538
2554
  const Changelog = 'Changelog';
@@ -3119,6 +3135,7 @@ const create = (uid, uri, x, y, width, height, platform, assetDir) => {
3119
3135
  features: [],
3120
3136
  featuresVirtualDom: [],
3121
3137
  focus: 0,
3138
+ focusedTabIndex: 0,
3122
3139
  folderSize: 0,
3123
3140
  hasColorTheme: false,
3124
3141
  hasReadme: false,
@@ -3167,7 +3184,7 @@ const isEqual$3 = (oldState, newState) => {
3167
3184
  };
3168
3185
 
3169
3186
  const isEqual$2 = (oldState, newState) => {
3170
- return oldState.focus === newState.focus;
3187
+ return oldState.focus === newState.focus && oldState.focusedTabIndex === newState.focusedTabIndex;
3171
3188
  };
3172
3189
 
3173
3190
  const isEqual$1 = (oldState, newState) => {
@@ -3185,9 +3202,10 @@ const RenderFocus = 2;
3185
3202
  const RenderItems = 3;
3186
3203
  const RenderScrollTop = 4;
3187
3204
  const RenderCss = 5;
3205
+ const RenderFocusContext = 6;
3188
3206
 
3189
- const modules = [isEqual$1, isEqual$2, isEqual, isEqual$3];
3190
- const numbers = [RenderItems, RenderFocus, RenderScrollTop, RenderCss];
3207
+ const modules = [isEqual$1, isEqual$2, isEqual, isEqual$3, isEqual$2];
3208
+ const numbers = [RenderItems, RenderFocus, RenderScrollTop, RenderCss, RenderFocusContext];
3191
3209
 
3192
3210
  const diff2 = uid => {
3193
3211
  const {
@@ -3215,6 +3233,41 @@ const executeCopy = async state => {
3215
3233
  return state;
3216
3234
  };
3217
3235
 
3236
+ const focusNextTab = state => {
3237
+ const {
3238
+ focusedTabIndex
3239
+ } = state;
3240
+ const newFocusedTabIndex = focusedTabIndex >= 1 ? 1 : focusedTabIndex + 1;
3241
+ return {
3242
+ ...state,
3243
+ focusedTabIndex: newFocusedTabIndex
3244
+ };
3245
+ };
3246
+
3247
+ const focusPreviousTab = state => {
3248
+ const {
3249
+ focusedTabIndex
3250
+ } = state;
3251
+ const newFocusedTabIndex = focusedTabIndex <= 0 ? 0 : focusedTabIndex - 1;
3252
+ return {
3253
+ ...state,
3254
+ focusedTabIndex: newFocusedTabIndex
3255
+ };
3256
+ };
3257
+
3258
+ const focusId$1 = 451;
3259
+ const getKeyBindings = () => {
3260
+ return [{
3261
+ command: 'ExtensionDetail.focusNextTab',
3262
+ key: RightArrow,
3263
+ when: focusId$1
3264
+ }, {
3265
+ command: 'ExtensionDetail.focusPreviousTab',
3266
+ key: LeftArrow,
3267
+ when: focusId$1
3268
+ }];
3269
+ };
3270
+
3218
3271
  const getMenuEntriesImage = (state, props) => {
3219
3272
  return [{
3220
3273
  args: [],
@@ -3379,7 +3432,7 @@ const handleClickCategory = async (state, categoryId) => {
3379
3432
  };
3380
3433
 
3381
3434
  const disableExtension = id => {
3382
- return disableExtension$1(id);
3435
+ return disable(id);
3383
3436
  };
3384
3437
 
3385
3438
  const Web = 1;
@@ -3405,6 +3458,12 @@ const getExtension$1 = async (id, platform) => {
3405
3458
  };
3406
3459
 
3407
3460
  const getExtensionNew = async id => {
3461
+ try {
3462
+ const rpc = get$3(ExtensionManagementWorker);
3463
+ return await rpc.invoke('Extensions.getExtension', id);
3464
+ } catch {
3465
+ // ignore
3466
+ }
3408
3467
  return getExtension$2(id);
3409
3468
  };
3410
3469
  const getExtension = async (id, platform) => {
@@ -3423,12 +3482,12 @@ const getExtensionDetailButtons = (hasColorTheme, isBuiltin, isDisabled) => {
3423
3482
  onClick: HandleClickSetColorTheme
3424
3483
  }, {
3425
3484
  enabled: isDisabled,
3426
- label: enable(),
3485
+ label: enable$1(),
3427
3486
  name: Enable,
3428
3487
  onClick: HandleClickEnable
3429
3488
  }, {
3430
3489
  enabled: !isDisabled,
3431
- label: disable(),
3490
+ label: disable$1(),
3432
3491
  name: Disable,
3433
3492
  onClick: HandleClickDisable
3434
3493
  }, {
@@ -3466,7 +3525,7 @@ const handleClickDisable = async state => {
3466
3525
  };
3467
3526
 
3468
3527
  const enableExtension = id => {
3469
- return enableExtension$1(id);
3528
+ return enable(id);
3470
3529
  };
3471
3530
 
3472
3531
  const handleClickEnable = async state => {
@@ -3569,155 +3628,253 @@ const handleClickUninstall = async state => {
3569
3628
  return state;
3570
3629
  };
3571
3630
 
3572
- const handleExtensionsStatusUpdate = async state => {
3573
- const details = await getRuntimeStatusDetails(state.extension);
3574
- return {
3575
- ...state,
3576
- ...details
3577
- };
3631
+ const existsFile = async uri => {
3632
+ try {
3633
+ return await exists(uri);
3634
+ } catch {
3635
+ return false;
3636
+ }
3578
3637
  };
3579
3638
 
3580
- const extensionDefaultIcon = assetDir => {
3581
- return `${assetDir}/icons/extensionDefaultIcon.png`;
3582
- };
3583
- const extensionLanguageBasics = assetDir => {
3584
- return `${assetDir}/icons/language-icon.svg`;
3639
+ class ExtensionNotFoundError extends Error {
3640
+ constructor(extensionId) {
3641
+ super(`extension not found: ${extensionId}`);
3642
+ this.name = 'ExtensionNotFoundError';
3643
+ }
3644
+ }
3645
+
3646
+ const getRemoteSrc = uri => {
3647
+ const src = `/remote${uri}`;
3648
+ return src;
3585
3649
  };
3586
- const extensionTheme = assetDir => {
3587
- return `${assetDir}/icons/theme-icon.png`;
3650
+
3651
+ const getBaseUrl = (extensionPath, platform) => {
3652
+ switch (platform) {
3653
+ case Electron:
3654
+ case Remote:
3655
+ return getRemoteSrc(extensionPath + '/');
3656
+ default:
3657
+ return extensionPath;
3658
+ }
3588
3659
  };
3589
3660
 
3590
- const handleIconError = state => {
3591
- const {
3592
- assetDir,
3593
- iconSrc
3594
- } = state;
3595
- if (iconSrc === extensionDefaultIcon(assetDir)) {
3596
- return state;
3661
+ const getCommit = async () => {
3662
+ try {
3663
+ const commit = await invoke$1('Layout.getCommit');
3664
+ return commit;
3665
+ } catch {
3666
+ return '';
3597
3667
  }
3598
- return {
3599
- ...state,
3600
- iconSrc: extensionDefaultIcon(assetDir)
3601
- };
3602
3668
  };
3603
3669
 
3604
- const ExtensionDetailIconContextMenu = 4091;
3670
+ const getExtensionIdFromUri = uri => {
3671
+ const id = uri.slice('extension-detail://'.length);
3672
+ return id;
3673
+ };
3605
3674
 
3606
- const handleImageContextMenu = async (state, eventX, eventY) => {
3607
- const {
3608
- uid
3609
- } = state;
3610
- await show2(uid, ExtensionDetailIconContextMenu, eventX, eventY, {
3611
- menuId: ExtensionDetailIconContextMenu
3612
- });
3613
- return state;
3675
+ const interpolate = (value, inMin, inMax, outMin, outMax) => {
3676
+ const clamped = Math.min(Math.max(value, inMin), inMax);
3677
+ const ratio = (clamped - inMin) / (inMax - inMin);
3678
+ const mapped = outMin + ratio * (outMax - outMin);
3679
+ return Math.round(mapped);
3614
3680
  };
3615
3681
 
3616
- const isExternalLink = href => {
3617
- return href.startsWith('http://') || href.startsWith('https://');
3682
+ const getPadding = width => {
3683
+ if (width < 600) {
3684
+ return 10;
3685
+ }
3686
+ if (width < 800) {
3687
+ return 10;
3688
+ }
3689
+ if (width < 1200) {
3690
+ return interpolate(width, 800, 1200, 10, 30);
3691
+ }
3692
+ return 30;
3618
3693
  };
3619
- const handleReadmeClick = async (state, nodeName, href) => {
3620
- if (!href || !isExternalLink(href)) {
3621
- return state;
3694
+ const getSideBarWidth = width => {
3695
+ if (width < 490) {
3696
+ return 0;
3622
3697
  }
3623
- // TODO what to do about relative links? open them in editor?
3624
- // TODO what to do about mail links?
3625
- await openUrl$1(href);
3626
- // TODO check node name and href
3627
- return state;
3698
+ if (width < 650) {
3699
+ return Math.max(175 + Math.round(20 * (width / 100)), Math.round(width / 4));
3700
+ }
3701
+ if (width < 800) {
3702
+ return Math.max(175 + Math.round(20 * (width / 100)), Math.round(width / 4));
3703
+ }
3704
+ return Math.max(175 + Math.round(20 * (width / 100)), Math.round(width / 4));
3628
3705
  };
3629
3706
 
3630
- const handleReadmeContextMenu = async (state, x, y, nodeName, href) => {
3631
- const {
3632
- uid
3633
- } = state;
3634
- // TODO maybe also pass other args
3635
- await show2(uid, ExtensionDetailReadme, x, y, {
3636
- href,
3637
- menuId: ExtensionDetailReadme,
3638
- nodeName
3639
- });
3640
- // TODO
3641
- return state;
3707
+ const getTabs = (selectedTab, hasReadme, hasFeatures, hasChangelog) => {
3708
+ const tabs = [{
3709
+ enabled: hasReadme,
3710
+ label: details(),
3711
+ name: Details,
3712
+ selected: selectedTab === Details
3713
+ }, {
3714
+ enabled: hasFeatures,
3715
+ label: features$1(),
3716
+ name: Features,
3717
+ selected: selectedTab === Features
3718
+ }, {
3719
+ enabled: hasChangelog,
3720
+ label: changelog(),
3721
+ name: Changelog,
3722
+ selected: selectedTab === Changelog
3723
+ }];
3724
+ return tabs;
3642
3725
  };
3643
3726
 
3644
- const handleScroll = (state, scrollTop, scrollSource = Script) => {
3645
- const newScrollTop = Math.max(0, scrollTop);
3646
- return {
3647
- ...state,
3648
- readmeScrollTop: newScrollTop,
3649
- scrollSource
3650
- };
3651
- };
3727
+ const Small = 1;
3728
+ const Normal = 2;
3729
+ const Large = 3;
3652
3730
 
3653
- const handleSelectionChange = async (state, selection) => {
3654
- // console.log('selection change')
3655
- return state;
3731
+ const getViewletSize = width => {
3732
+ if (width < 180) {
3733
+ return Small;
3734
+ }
3735
+ if (width < 768) {
3736
+ return Normal;
3737
+ }
3738
+ return Large;
3656
3739
  };
3657
3740
 
3658
- const readFile = async uri => {
3659
- return readFile$1(uri);
3741
+ const extensionDefaultIcon = assetDir => {
3742
+ return `${assetDir}/icons/extensionDefaultIcon.png`;
3660
3743
  };
3661
-
3662
- const ENOENT = 'ENOENT';
3663
-
3664
- const isEnoentError = error => {
3665
- return error && error.code === ENOENT;
3744
+ const extensionLanguageBasics = assetDir => {
3745
+ return `${assetDir}/icons/language-icon.svg`;
3666
3746
  };
3667
-
3668
- const error = async error => {
3669
- // TODO send message to error worker or log worker
3670
- // @ts-ignore
3671
- console.error(error);
3747
+ const extensionTheme = assetDir => {
3748
+ return `${assetDir}/icons/theme-icon.png`;
3672
3749
  };
3673
3750
 
3674
- const join = (...parts) => {
3675
- return parts.join('/');
3751
+ const isLanguageBasicsExtension = extension => {
3752
+ return extension.name && extension.name.startsWith('Language Basics');
3676
3753
  };
3677
3754
 
3678
- const loadChangelogContent = async path => {
3679
- try {
3680
- const changelogUrl = join(path, 'CHANGELOG.md');
3681
- const changelogContent = await readFile(changelogUrl);
3682
- return changelogContent;
3683
- } catch (error$1) {
3684
- if (isEnoentError(error$1)) {
3685
- return '';
3686
- }
3687
- await error(new VError(error$1, 'Failed to load Changelog content'));
3688
- return `${error$1}`;
3689
- }
3755
+ const isThemeExtension = extension => {
3756
+ return extension.name && extension.name.endsWith(' Theme');
3690
3757
  };
3691
3758
 
3692
- const selectTabChangelog = async state => {
3693
- const {
3694
- baseUrl,
3695
- extension,
3696
- locationProtocol,
3697
- tabs
3759
+ const getIcon = (extension, platform, assetDir) => {
3760
+ if (!extension) {
3761
+ return extensionDefaultIcon(assetDir);
3762
+ }
3763
+ if (!extension.path || !extension.icon) {
3764
+ if (isLanguageBasicsExtension(extension)) {
3765
+ return extensionLanguageBasics(assetDir);
3766
+ }
3767
+ if (isThemeExtension(extension)) {
3768
+ return extensionTheme(assetDir);
3769
+ }
3770
+ return extensionDefaultIcon(assetDir);
3771
+ }
3772
+ if (platform === Remote || platform === Electron) {
3773
+ if (extension.builtin) {
3774
+ return `${assetDir}/extensions/${extension.id}/${extension.icon}`;
3775
+ }
3776
+ return `/remote/${extension.path}/${extension.icon}`; // TODO support windows paths
3777
+ }
3778
+ return '';
3779
+ };
3780
+
3781
+ const getDescription = extension => {
3782
+ if (!extension || !extension.description) {
3783
+ return 'n/a';
3784
+ }
3785
+ return extension.description;
3786
+ };
3787
+
3788
+ const getName = extension => {
3789
+ if (extension && extension.name) {
3790
+ return extension.name;
3791
+ }
3792
+ if (extension && extension.id) {
3793
+ return extension.id;
3794
+ }
3795
+ return 'n/a';
3796
+ };
3797
+
3798
+ const getDownloadCount = extension => {
3799
+ if (!extension) {
3800
+ return 'n/a';
3801
+ }
3802
+
3803
+ // Check for download count in various possible locations
3804
+ const downloadCount = extension.downloadCount || extension.downloads || extension.marketplace?.downloadCount || extension.marketplace?.downloads || extension.packageJSON?.downloadCount || extension.packageJSON?.downloads;
3805
+ if (!downloadCount) {
3806
+ return 'n/a';
3807
+ }
3808
+
3809
+ // Format the number with commas for better readability
3810
+ return downloadCount.toLocaleString();
3811
+ };
3812
+
3813
+ const getRating = extension => {
3814
+ if (!extension) {
3815
+ return 'n/a';
3816
+ }
3817
+
3818
+ // Check for rating in various possible locations
3819
+ const rating = extension.rating || extension.averageRating || extension.marketplace?.rating || extension.marketplace?.averageRating || extension.packageJSON?.rating || extension.packageJSON?.averageRating;
3820
+ if (!rating) {
3821
+ return 'n/a';
3822
+ }
3823
+
3824
+ // Format rating to one decimal place
3825
+ return rating.toFixed(1);
3826
+ };
3827
+
3828
+ const getBadge = (builtin, badgeEnabled) => {
3829
+ if (builtin && badgeEnabled) {
3830
+ return 'builtin';
3831
+ }
3832
+ return '';
3833
+ };
3834
+
3835
+ const hasColorThemes = extension => {
3836
+ return Boolean(extension && extension.colorThemes && extension.colorThemes.length > 0);
3837
+ };
3838
+
3839
+ const loadHeaderContent = (state, platform, extension) => {
3840
+ const {
3841
+ assetDir,
3842
+ builtinExtensionsBadgeEnabled
3698
3843
  } = state;
3699
- const changelogContent = await loadChangelogContent(extension.path); // TODO use uri
3700
- const changelogMarkdownHtml = await renderMarkdown(changelogContent, {
3701
- baseUrl,
3702
- locationProtocol
3703
- });
3704
- const changelogDom = await getMarkdownVirtualDom(changelogMarkdownHtml);
3705
- const newTabs = tabs.map(tab => {
3706
- return {
3707
- ...tab,
3708
- selected: tab.name === Changelog
3709
- };
3710
- });
3844
+ const iconSrc = getIcon(extension, platform, assetDir);
3845
+ const description = getDescription(extension);
3846
+ const name = getName(extension);
3847
+ const extensionUri = extension.uri || extension.path;
3848
+ const extensionId = extension?.id || 'n/a';
3849
+ const extensionVersion = extension?.version || 'n/a';
3850
+ const hasColorTheme = hasColorThemes(extension);
3851
+ const isBuiltin = extension?.builtin;
3852
+ const badge = getBadge(isBuiltin, builtinExtensionsBadgeEnabled);
3853
+ const downloadCount = getDownloadCount(extension);
3854
+ const rating = getRating(extension);
3711
3855
  return {
3712
- ...state,
3713
- changelogVirtualDom: changelogDom,
3714
- selectedTab: Changelog,
3715
- tabs: newTabs
3856
+ badge,
3857
+ description,
3858
+ downloadCount,
3859
+ extension,
3860
+ extensionId,
3861
+ extensionUri,
3862
+ extensionVersion,
3863
+ hasColorTheme,
3864
+ iconSrc,
3865
+ name,
3866
+ rating
3716
3867
  };
3717
3868
  };
3718
3869
 
3719
- const selectTabDefault = async state => {
3720
- return state;
3870
+ const readFile = async uri => {
3871
+ return readFile$1(uri);
3872
+ };
3873
+
3874
+ const ENOENT = 'ENOENT';
3875
+
3876
+ const isEnoentError = error => {
3877
+ return error && error.code === ENOENT;
3721
3878
  };
3722
3879
 
3723
3880
  const loadReadmeContent = async readmeUrl => {
@@ -3735,104 +3892,174 @@ const loadReadmeContent = async readmeUrl => {
3735
3892
  }
3736
3893
  };
3737
3894
 
3738
- const selectTabDetails = async state => {
3739
- const {
3740
- baseUrl,
3741
- locationProtocol,
3742
- readmeUrl,
3743
- tabs
3744
- } = state;
3745
- const readmeContent = await loadReadmeContent(readmeUrl);
3746
- const readmeHtml = await renderMarkdown(readmeContent, {
3747
- baseUrl,
3748
- linksExternal: true,
3749
- locationProtocol
3750
- });
3751
- const detailsDom = await getMarkdownVirtualDom(readmeHtml);
3752
- const newTabs = tabs.map(tab => {
3753
- return {
3754
- ...tab,
3755
- selected: tab.name === Details
3756
- };
3757
- });
3758
- return {
3759
- ...state,
3760
- detailsVirtualDom: detailsDom,
3761
- selectedTab: Details,
3762
- tabs: newTabs
3763
- };
3764
- };
3765
-
3766
- const selectTabFeatures = async state => {
3767
- const {
3768
- baseUrl,
3769
- extension,
3770
- features,
3771
- locationProtocol,
3772
- selectedFeature,
3773
- tabs
3774
- } = state;
3775
- if (features.length === 0) {
3776
- return state;
3895
+ const stringifyCategory = category => {
3896
+ if (typeof category === 'string') {
3897
+ return category;
3777
3898
  }
3778
- const actualSelectedFeature = selectedFeature || Theme;
3779
- const fn = getFeatureDetailsHandler(actualSelectedFeature);
3780
- const partialNewState = await fn(extension, baseUrl, locationProtocol);
3781
- const newTabs = tabs.map(tab => {
3782
- return {
3783
- ...tab,
3784
- selected: tab.name === Features
3785
- };
3786
- });
3787
- const newFeatures = features.map(feature => {
3788
- if (feature.id === actualSelectedFeature) {
3789
- return {
3790
- ...feature,
3791
- selected: true
3792
- };
3793
- }
3794
- return {
3795
- ...feature,
3796
- selected: false
3797
- };
3798
- });
3899
+ return JSON.stringify(category);
3900
+ };
3901
+ const toCategory = categoryString => {
3799
3902
  return {
3800
- ...state,
3801
- ...partialNewState,
3802
- features: newFeatures,
3803
- selectedFeature: features[0].id || '',
3804
- selectedTab: Features,
3805
- tabs: newTabs
3903
+ id: categoryString.toLowerCase(),
3904
+ label: categoryString
3806
3905
  };
3807
3906
  };
3808
-
3809
- const getSelectTabHandler = name => {
3810
- switch (name) {
3811
- case Changelog:
3812
- return selectTabChangelog;
3813
- case Details:
3814
- return selectTabDetails;
3815
- case Features:
3816
- return selectTabFeatures;
3817
- default:
3818
- return selectTabDefault;
3907
+ const getCategories = extension => {
3908
+ if (!hasProperty(extension, 'categories') || !Array.isArray(extension.categories)) {
3909
+ return [];
3819
3910
  }
3911
+ const categoryStrings = extension.categories.map(stringifyCategory);
3912
+ const categories = categoryStrings.map(toCategory);
3913
+ return categories;
3820
3914
  };
3821
3915
 
3822
- const selectTab = (state, name) => {
3823
- const fn = getSelectTabHandler(name);
3824
- return fn(state);
3825
- };
3916
+ const BYTE_UNITS = ['B', 'kB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'];
3917
+ const BIBYTE_UNITS = ['B', 'KiB', 'MiB', 'GiB', 'TiB', 'PiB', 'EiB', 'ZiB', 'YiB'];
3918
+ const BIT_UNITS = ['b', 'kbit', 'Mbit', 'Gbit', 'Tbit', 'Pbit', 'Ebit', 'Zbit', 'Ybit'];
3919
+ const BIBIT_UNITS = ['b', 'kibit', 'Mibit', 'Gibit', 'Tibit', 'Pibit', 'Eibit', 'Zibit', 'Yibit'];
3826
3920
 
3827
- const handleTabsClick = (state, name) => {
3828
- return selectTab(state, name);
3921
+ /*
3922
+ Formats the given number using `Number#toLocaleString`.
3923
+ - If locale is a string, the value is expected to be a locale-key (for example: `de`).
3924
+ - If locale is true, the system default locale is used for translation.
3925
+ - If no value for locale is specified, the number is returned unmodified.
3926
+ */
3927
+ const toLocaleString = (number, locale, options) => {
3928
+ let result = number;
3929
+ if (typeof locale === 'string' || Array.isArray(locale)) {
3930
+ result = number.toLocaleString(locale, options);
3931
+ } else if (locale === true || options !== undefined) {
3932
+ result = number.toLocaleString(undefined, options);
3933
+ }
3934
+ return result;
3829
3935
  };
3830
-
3831
- const getSizeEntries = (showSizeLink, displaySize, extensionUri) => {
3832
- if (!showSizeLink) {
3833
- return [];
3936
+ const log10 = numberOrBigInt => {
3937
+ if (typeof numberOrBigInt === 'number') {
3938
+ return Math.log10(numberOrBigInt);
3834
3939
  }
3835
- return [{
3940
+ const string = numberOrBigInt.toString(10);
3941
+ return string.length + Math.log10(`0.${string.slice(0, 15)}`);
3942
+ };
3943
+ const log = numberOrBigInt => {
3944
+ if (typeof numberOrBigInt === 'number') {
3945
+ return Math.log(numberOrBigInt);
3946
+ }
3947
+ return log10(numberOrBigInt) * Math.log(10);
3948
+ };
3949
+ const divide = (numberOrBigInt, divisor) => {
3950
+ if (typeof numberOrBigInt === 'number') {
3951
+ return numberOrBigInt / divisor;
3952
+ }
3953
+ const integerPart = numberOrBigInt / BigInt(divisor);
3954
+ const remainder = numberOrBigInt % BigInt(divisor);
3955
+ return Number(integerPart) + Number(remainder) / divisor;
3956
+ };
3957
+ const applyFixedWidth = (result, fixedWidth) => {
3958
+ if (fixedWidth === undefined) {
3959
+ return result;
3960
+ }
3961
+ if (typeof fixedWidth !== 'number' || !Number.isSafeInteger(fixedWidth) || fixedWidth < 0) {
3962
+ throw new TypeError(`Expected fixedWidth to be a non-negative integer, got ${typeof fixedWidth}: ${fixedWidth}`);
3963
+ }
3964
+ if (fixedWidth === 0) {
3965
+ return result;
3966
+ }
3967
+ return result.length < fixedWidth ? result.padStart(fixedWidth, ' ') : result;
3968
+ };
3969
+ const buildLocaleOptions = options => {
3970
+ const {
3971
+ minimumFractionDigits,
3972
+ maximumFractionDigits
3973
+ } = options;
3974
+ if (minimumFractionDigits === undefined && maximumFractionDigits === undefined) {
3975
+ return undefined;
3976
+ }
3977
+ return {
3978
+ ...(minimumFractionDigits !== undefined && {
3979
+ minimumFractionDigits
3980
+ }),
3981
+ ...(maximumFractionDigits !== undefined && {
3982
+ maximumFractionDigits
3983
+ }),
3984
+ roundingMode: 'trunc'
3985
+ };
3986
+ };
3987
+ function prettyBytes(number, options) {
3988
+ if (typeof number !== 'bigint' && !Number.isFinite(number)) {
3989
+ throw new TypeError(`Expected a finite number, got ${typeof number}: ${number}`);
3990
+ }
3991
+ options = {
3992
+ bits: false,
3993
+ binary: false,
3994
+ space: true,
3995
+ nonBreakingSpace: false,
3996
+ ...options
3997
+ };
3998
+ const UNITS = options.bits ? options.binary ? BIBIT_UNITS : BIT_UNITS : options.binary ? BIBYTE_UNITS : BYTE_UNITS;
3999
+ const separator = options.space ? options.nonBreakingSpace ? '\u00A0' : ' ' : '';
4000
+
4001
+ // Handle signed zero case
4002
+ const isZero = typeof number === 'number' ? number === 0 : number === 0n;
4003
+ if (options.signed && isZero) {
4004
+ const result = ` 0${separator}${UNITS[0]}`;
4005
+ return applyFixedWidth(result, options.fixedWidth);
4006
+ }
4007
+ const isNegative = number < 0;
4008
+ const prefix = isNegative ? '-' : options.signed ? '+' : '';
4009
+ if (isNegative) {
4010
+ number = -number;
4011
+ }
4012
+ const localeOptions = buildLocaleOptions(options);
4013
+ let result;
4014
+ if (number < 1) {
4015
+ const numberString = toLocaleString(number, options.locale, localeOptions);
4016
+ result = prefix + numberString + separator + UNITS[0];
4017
+ } else {
4018
+ const exponent = Math.min(Math.floor(options.binary ? log(number) / Math.log(1024) : log10(number) / 3), UNITS.length - 1);
4019
+ number = divide(number, (options.binary ? 1024 : 1000) ** exponent);
4020
+ if (!localeOptions) {
4021
+ const minPrecision = Math.max(3, Math.floor(number).toString().length);
4022
+ number = number.toPrecision(minPrecision);
4023
+ }
4024
+ const numberString = toLocaleString(Number(number), options.locale, localeOptions);
4025
+ const unit = UNITS[exponent];
4026
+ result = prefix + numberString + separator + unit;
4027
+ }
4028
+ return applyFixedWidth(result, options.fixedWidth);
4029
+ }
4030
+
4031
+ const getDisplaySize = size => {
4032
+ return prettyBytes(size, {
4033
+ maximumFractionDigits: 1
4034
+ });
4035
+ };
4036
+
4037
+ const supportsFileSize = uri => {
4038
+ if (uri.startsWith('http:') || uri.startsWith('https://')) {
4039
+ return false;
4040
+ }
4041
+ return true;
4042
+ };
4043
+ const getFolderSize = async uri => {
4044
+ if (!uri) {
4045
+ throw new VError(`uri is required`);
4046
+ }
4047
+ if (!supportsFileSize(uri)) {
4048
+ return 0;
4049
+ }
4050
+ try {
4051
+ // @ts-ignore
4052
+ return await invoke('FileSystem.getFolderSize', uri);
4053
+ } catch {
4054
+ return 0;
4055
+ }
4056
+ };
4057
+
4058
+ const getSizeEntries = (showSizeLink, displaySize, extensionUri) => {
4059
+ if (!showSizeLink) {
4060
+ return [];
4061
+ }
4062
+ return [{
3836
4063
  key: size(),
3837
4064
  onClick: HandleClickSize,
3838
4065
  title: extensionUri,
@@ -3858,739 +4085,610 @@ const getInstallationEntries = (displaySize, extensionId, extensionVersion, exte
3858
4085
  return entries;
3859
4086
  };
3860
4087
 
3861
- const hideSizeLink = state => {
3862
- const {
3863
- displaySize,
3864
- extensionId,
3865
- extensionUri,
3866
- extensionVersion
3867
- } = state;
3868
- const newShowSizeLink = false;
3869
- const installationEntries = getInstallationEntries(displaySize, extensionId, extensionVersion, extensionUri, newShowSizeLink);
3870
- return {
3871
- ...state,
3872
- installationEntries,
3873
- showSizeLink: newShowSizeLink
3874
- };
4088
+ const getMarketplaceEntries = isBuiltin => {
4089
+ if (isBuiltin) {
4090
+ return [];
4091
+ }
4092
+ return [{
4093
+ key: published(),
4094
+ odd: true,
4095
+ value: 'n/a'
4096
+ }, {
4097
+ key: lastReleased(),
4098
+ value: 'n/a'
4099
+ }];
3875
4100
  };
3876
4101
 
3877
- const sendMessagePortToExtensionHostWorker = async port => {
3878
- await sendMessagePortToExtensionHostWorker$1(port, 0);
4102
+ const getLicenseLink = extension => {
4103
+ // TODO
4104
+ return '#';
3879
4105
  };
3880
4106
 
3881
- const createExtensionHostWorkerRpc = async () => {
4107
+ const ensureValidLink = link => {
4108
+ if (!link) {
4109
+ return '';
4110
+ }
3882
4111
  try {
3883
- const rpc = await TransferMessagePortRpcParent.create({
3884
- commandMap: {},
3885
- send: sendMessagePortToExtensionHostWorker
3886
- });
3887
- return rpc;
3888
- } catch (error) {
3889
- throw new VError(error, `Failed to create extension host rpc`);
4112
+ const parsed = new URL(link);
4113
+ if (parsed.protocol !== 'https:') {
4114
+ return '';
4115
+ }
4116
+ return link;
4117
+ } catch {
4118
+ return '';
3890
4119
  }
3891
4120
  };
3892
4121
 
3893
- const initializeExtensionHostWorker = async () => {
3894
- const rpc = await createExtensionHostWorkerRpc();
3895
- set$4(rpc);
4122
+ const getRepositoryLinkRaw = extension => {
4123
+ if (extension && hasProperty(extension, 'repository') && typeof extension.repository === 'string') {
4124
+ return extension.repository; // TODO watch out for javascript: or other invalid links or path traversal
4125
+ }
4126
+ return '';
4127
+ };
4128
+ const getRepositoryLink = extension => {
4129
+ const raw = getRepositoryLinkRaw(extension);
4130
+ const validLink = ensureValidLink(raw);
4131
+ return validLink;
4132
+ };
4133
+ const getIssuesLink = extension => {
4134
+ const repositoryLink = getRepositoryLink(extension);
4135
+ if (!repositoryLink) {
4136
+ return '';
4137
+ }
4138
+ if (repositoryLink && repositoryLink.startsWith('https://github.com')) {
4139
+ return `${repositoryLink}/issues`;
4140
+ }
4141
+ return '';
3896
4142
  };
3897
4143
 
3898
- const sendMessagePortToFileSystemWorker = async port => {
3899
- await sendMessagePortToFileSystemWorker$1(port, 0);
4144
+ const getResources = (isBuiltin, extension) => {
4145
+ const repositoryLink = getRepositoryLink(extension);
4146
+ const issueLink = getIssuesLink(extension);
4147
+ const licenseLink = getLicenseLink();
4148
+ // TODO hide marketplace link for builtin extensions
4149
+ return [{
4150
+ icon: 'LinkExternal',
4151
+ label: marketplace(),
4152
+ url: '#'
4153
+ }, {
4154
+ icon: 'LinkExternal',
4155
+ label: issues(),
4156
+ url: issueLink
4157
+ }, {
4158
+ icon: 'Repo',
4159
+ label: repository(),
4160
+ url: repositoryLink
4161
+ }, {
4162
+ icon: 'LinkExternal',
4163
+ label: license(),
4164
+ url: licenseLink
4165
+ }];
3900
4166
  };
3901
4167
 
3902
- const createFileSystemWorkerRpc = async () => {
3903
- try {
3904
- const rpc = await TransferMessagePortRpcParent.create({
3905
- commandMap: {},
3906
- send: sendMessagePortToFileSystemWorker
3907
- });
3908
- return rpc;
3909
- } catch (error) {
3910
- throw new VError(error, `Failed to create file system worker rpc`);
3911
- }
4168
+ const loadSideBarContent = async (extensionId, extensionVersion, extensionUri, isBuiltin, extension, showSizeLink) => {
4169
+ const folderSize = await getFolderSize(extensionUri);
4170
+ const displaySize = getDisplaySize(folderSize);
4171
+ const installationEntries = getInstallationEntries(displaySize, extensionId, extensionVersion, extensionUri, showSizeLink);
4172
+ const marketplaceEntries = getMarketplaceEntries(isBuiltin);
4173
+ const categories = getCategories(extension);
4174
+ const resources = getResources(isBuiltin, extension);
4175
+ return {
4176
+ categories,
4177
+ displaySize,
4178
+ folderSize,
4179
+ installationEntries,
4180
+ marketplaceEntries,
4181
+ resources
4182
+ };
3912
4183
  };
3913
4184
 
3914
- const initializeFileSystemWorker = async () => {
3915
- const rpc = await createFileSystemWorkerRpc();
3916
- set$1(rpc);
4185
+ const join = (...parts) => {
4186
+ return parts.join('/');
3917
4187
  };
3918
4188
 
3919
- const sendMessagePortToMarkdownWorker = async port => {
3920
- await sendMessagePortToMarkdownWorker$1(port, 0);
4189
+ const getSavedChangelogScrollTop = savedState => {
4190
+ if (savedState && typeof savedState === 'object' && 'changelogScrollTop' in savedState && typeof savedState.changelogScrollTop === 'number') {
4191
+ return savedState.changelogScrollTop;
4192
+ }
4193
+ return 0;
3921
4194
  };
3922
4195
 
3923
- const createMarkdownWorkerRpc = async () => {
3924
- try {
3925
- const rpc = await TransferMessagePortRpcParent.create({
3926
- commandMap: {},
3927
- send: sendMessagePortToMarkdownWorker
3928
- });
3929
- return rpc;
3930
- } catch (error) {
3931
- throw new VError(error, `Failed to create markdown worker rpc`);
4196
+ const getSavedReadmeScrollTop = savedState => {
4197
+ if (savedState && typeof savedState === 'object' && 'readmeScrollTop' in savedState && typeof savedState.readmeScrollTop === 'number') {
4198
+ return savedState.readmeScrollTop;
3932
4199
  }
4200
+ return 0;
3933
4201
  };
3934
4202
 
3935
- const initializeMarkdownWorker = async () => {
3936
- const rpc = await createMarkdownWorkerRpc();
3937
- set$3(rpc);
4203
+ const getSavedSelectedFeature = savedState => {
4204
+ if (savedState && typeof savedState === 'object' && 'selectedFeature' in savedState && typeof savedState.selectedFeature === 'string') {
4205
+ return savedState.selectedFeature;
4206
+ }
4207
+ return Details;
3938
4208
  };
3939
4209
 
3940
- const initialize = async () => {
3941
- // TODO load markdown worker only when needed
3942
- await Promise.all([initializeMarkdownWorker(), initializeFileSystemWorker(), initializeExtensionHostWorker()]);
4210
+ const getSavedSelectedTab = savedState => {
4211
+ if (savedState && typeof savedState === 'object' && 'selectedTab' in savedState && typeof savedState.selectedTab === 'string') {
4212
+ return savedState.selectedTab;
4213
+ }
4214
+ return Details;
3943
4215
  };
3944
4216
 
3945
- const existsFile = async uri => {
3946
- try {
3947
- return await exists(uri);
3948
- } catch {
3949
- return false;
3950
- }
4217
+ const restoreState = savedState => {
4218
+ const selectedTab = getSavedSelectedTab(savedState);
4219
+ const selectedFeature = getSavedSelectedFeature(savedState);
4220
+ const readmeScrollTop = getSavedReadmeScrollTop(savedState);
4221
+ const changelogScrollTop = getSavedChangelogScrollTop(savedState);
4222
+ return {
4223
+ changelogScrollTop,
4224
+ readmeScrollTop,
4225
+ selectedFeature,
4226
+ selectedTab
4227
+ };
3951
4228
  };
3952
4229
 
3953
- class ExtensionNotFoundError extends Error {
3954
- constructor(extensionId) {
3955
- super(`extension not found: ${extensionId}`);
3956
- this.name = 'ExtensionNotFoundError';
4230
+ const isEnabled = tab => {
4231
+ return tab.enabled;
4232
+ };
4233
+ const loadContent = async (state, platform, savedState, isTest = false) => {
4234
+ if (isTest) {
4235
+ savedState = undefined;
3957
4236
  }
3958
- }
4237
+ const {
4238
+ uri,
4239
+ width
4240
+ } = state;
4241
+ const id = getExtensionIdFromUri(uri);
4242
+ const extension = await getExtension(id, platform);
4243
+ if (!extension) {
4244
+ throw new ExtensionNotFoundError(id);
4245
+ }
4246
+ const commit = await getCommit();
4247
+ const headerData = loadHeaderContent(state, platform, extension);
4248
+ const {
4249
+ badge,
4250
+ description,
4251
+ downloadCount,
4252
+ extensionId,
4253
+ extensionUri,
4254
+ extensionVersion,
4255
+ hasColorTheme,
4256
+ iconSrc,
4257
+ name,
4258
+ rating
4259
+ } = headerData;
4260
+ const readmeUrl = join(extensionUri, 'README.md');
4261
+ const changelogUrl = join(extensionUri, 'CHANGELOG.md');
4262
+ const [hasReadme, hasChangelog] = await Promise.all([existsFile(readmeUrl), existsFile(changelogUrl)]);
4263
+ const readmeContent = hasReadme ? await loadReadmeContent(readmeUrl) : noReadmeFound();
4264
+ const baseUrl = getBaseUrl(extension.path, platform);
4265
+ const locationProtocol = location.protocol;
4266
+ const locationHost = location.host;
4267
+ const readmeHtml = await renderMarkdown(readmeContent, {
4268
+ baseUrl,
4269
+ commit,
4270
+ linksExternal: true,
4271
+ locationProtocol
4272
+ });
4273
+ const detailsVirtualDom = await getMarkdownVirtualDom(readmeHtml, {
4274
+ scrollToTopEnabled: true
4275
+ });
4276
+ const isBuiltin = extension?.isBuiltin;
4277
+ const disabled = extension?.disabled;
4278
+ const buttons = getExtensionDetailButtons(hasColorTheme, isBuiltin, disabled);
4279
+ const size = getViewletSize(width);
4280
+ const {
4281
+ changelogScrollTop,
4282
+ readmeScrollTop,
4283
+ selectedFeature,
4284
+ selectedTab
4285
+ } = restoreState(savedState);
4286
+ const features = getFeatures(selectedFeature || Theme, extension);
4287
+ const hasFeatures = features.length > 0;
4288
+ const tabs = getTabs(selectedTab, hasReadme, hasFeatures, hasChangelog);
4289
+ const enabledTabs = tabs.filter(isEnabled);
4290
+ const sizeValue = getViewletSize(width || 0);
4291
+ const showSizeLink = platform !== Web$1;
4292
+ const {
4293
+ categories,
4294
+ displaySize,
4295
+ folderSize,
4296
+ installationEntries,
4297
+ marketplaceEntries,
4298
+ resources
4299
+ } = await loadSideBarContent(extensionId, extensionVersion, extensionUri, isBuiltin, extension, showSizeLink);
4300
+ const padding = getPadding(width);
4301
+ const sideBarWidth = getSideBarWidth(width);
4302
+ const showSideBar = sideBarWidth > 0;
4303
+ return {
4304
+ ...state,
4305
+ badge,
4306
+ baseUrl,
4307
+ buttons,
4308
+ categories,
4309
+ changelogScrollTop,
4310
+ commit,
4311
+ description,
4312
+ detailsVirtualDom,
4313
+ disabled,
4314
+ displaySize,
4315
+ downloadCount,
4316
+ extension,
4317
+ extensionId,
4318
+ extensionUri,
4319
+ extensionVersion,
4320
+ features,
4321
+ folderSize,
4322
+ hasColorTheme,
4323
+ hasReadme,
4324
+ iconSrc,
4325
+ installationEntries,
4326
+ locationHost,
4327
+ locationProtocol,
4328
+ marketplaceEntries,
4329
+ name,
4330
+ paddingLeft: padding,
4331
+ paddingRight: padding,
4332
+ rating,
4333
+ readmeScrollTop,
4334
+ readmeUrl,
4335
+ resources,
4336
+ scrollSource: Script,
4337
+ scrollToTopButtonEnabled: true,
4338
+ selectedTab,
4339
+ showSideBar,
4340
+ showSizeLink,
4341
+ sideBarWidth,
4342
+ sizeOnDisk: size,
4343
+ sizeValue,
4344
+ tabs: enabledTabs
4345
+ };
4346
+ };
3959
4347
 
3960
- const getRemoteSrc = uri => {
3961
- const src = `/remote${uri}`;
3962
- return src;
4348
+ const handleExtensionsChanged = async state => {
4349
+ const {
4350
+ platform
4351
+ } = state;
4352
+ return loadContent(state, platform, {});
3963
4353
  };
3964
4354
 
3965
- const getBaseUrl = (extensionPath, platform) => {
3966
- switch (platform) {
3967
- case Electron:
3968
- case Remote:
3969
- return getRemoteSrc(extensionPath + '/');
3970
- default:
3971
- return extensionPath;
3972
- }
4355
+ const handleExtensionsStatusUpdate = async state => {
4356
+ const {
4357
+ extension
4358
+ } = state;
4359
+ const details = await getRuntimeStatusDetails(extension);
4360
+ return {
4361
+ ...state,
4362
+ ...details
4363
+ };
3973
4364
  };
3974
4365
 
3975
- const getCommit = async () => {
3976
- try {
3977
- const commit = await invoke$1('Layout.getCommit');
3978
- return commit;
3979
- } catch {
3980
- return '';
4366
+ const handleIconError = state => {
4367
+ const {
4368
+ assetDir,
4369
+ iconSrc
4370
+ } = state;
4371
+ if (iconSrc === extensionDefaultIcon(assetDir)) {
4372
+ return state;
3981
4373
  }
4374
+ return {
4375
+ ...state,
4376
+ iconSrc: extensionDefaultIcon(assetDir)
4377
+ };
3982
4378
  };
3983
4379
 
3984
- const getExtensionIdFromUri = uri => {
3985
- const id = uri.slice('extension-detail://'.length);
3986
- return id;
3987
- };
4380
+ const ExtensionDetailIconContextMenu = 4091;
3988
4381
 
3989
- const interpolate = (value, inMin, inMax, outMin, outMax) => {
3990
- const clamped = Math.min(Math.max(value, inMin), inMax);
3991
- const ratio = (clamped - inMin) / (inMax - inMin);
3992
- const mapped = outMin + ratio * (outMax - outMin);
3993
- return Math.round(mapped);
4382
+ const handleImageContextMenu = async (state, eventX, eventY) => {
4383
+ const {
4384
+ uid
4385
+ } = state;
4386
+ await show2(uid, ExtensionDetailIconContextMenu, eventX, eventY, {
4387
+ menuId: ExtensionDetailIconContextMenu
4388
+ });
4389
+ return state;
3994
4390
  };
3995
4391
 
3996
- const getPadding = width => {
3997
- if (width < 600) {
3998
- return 10;
3999
- }
4000
- if (width < 800) {
4001
- return 10;
4002
- }
4003
- if (width < 1200) {
4004
- return interpolate(width, 800, 1200, 10, 30);
4005
- }
4006
- return 30;
4392
+ const isExternalLink = href => {
4393
+ return href.startsWith('http://') || href.startsWith('https://');
4007
4394
  };
4008
- const getSideBarWidth = width => {
4009
- if (width < 490) {
4010
- return 0;
4011
- }
4012
- if (width < 650) {
4013
- return Math.max(175 + Math.round(20 * (width / 100)), Math.round(width / 4));
4014
- }
4015
- if (width < 800) {
4016
- return Math.max(175 + Math.round(20 * (width / 100)), Math.round(width / 4));
4395
+ const handleReadmeClick = async (state, nodeName, href) => {
4396
+ if (!href || !isExternalLink(href)) {
4397
+ return state;
4017
4398
  }
4018
- return Math.max(175 + Math.round(20 * (width / 100)), Math.round(width / 4));
4399
+ // TODO what to do about relative links? open them in editor?
4400
+ // TODO what to do about mail links?
4401
+ await openUrl$1(href);
4402
+ // TODO check node name and href
4403
+ return state;
4019
4404
  };
4020
4405
 
4021
- const getTabs = (selectedTab, hasReadme, hasFeatures, hasChangelog) => {
4022
- const tabs = [{
4023
- enabled: hasReadme,
4024
- label: details(),
4025
- name: Details,
4026
- selected: selectedTab === Details
4027
- }, {
4028
- enabled: hasFeatures,
4029
- label: features$1(),
4030
- name: Features,
4031
- selected: selectedTab === Features
4032
- }, {
4033
- enabled: hasChangelog,
4034
- label: changelog(),
4035
- name: Changelog,
4036
- selected: selectedTab === Changelog
4037
- }];
4038
- return tabs;
4039
- };
4040
-
4041
- const Small = 1;
4042
- const Normal = 2;
4043
- const Large = 3;
4044
-
4045
- const getViewletSize = width => {
4046
- if (width < 180) {
4047
- return Small;
4048
- }
4049
- if (width < 768) {
4050
- return Normal;
4051
- }
4052
- return Large;
4053
- };
4054
-
4055
- const isLanguageBasicsExtension = extension => {
4056
- return extension.name && extension.name.startsWith('Language Basics');
4057
- };
4058
-
4059
- const isThemeExtension = extension => {
4060
- return extension.name && extension.name.endsWith(' Theme');
4061
- };
4062
-
4063
- const getIcon = (extension, platform, assetDir) => {
4064
- if (!extension) {
4065
- return extensionDefaultIcon(assetDir);
4066
- }
4067
- if (!extension.path || !extension.icon) {
4068
- if (isLanguageBasicsExtension(extension)) {
4069
- return extensionLanguageBasics(assetDir);
4070
- }
4071
- if (isThemeExtension(extension)) {
4072
- return extensionTheme(assetDir);
4073
- }
4074
- return extensionDefaultIcon(assetDir);
4075
- }
4076
- if (platform === Remote || platform === Electron) {
4077
- if (extension.builtin) {
4078
- return `${assetDir}/extensions/${extension.id}/${extension.icon}`;
4079
- }
4080
- return `/remote/${extension.path}/${extension.icon}`; // TODO support windows paths
4081
- }
4082
- return '';
4406
+ const handleReadmeContextMenu = async (state, x, y, nodeName, href) => {
4407
+ const {
4408
+ uid
4409
+ } = state;
4410
+ // TODO maybe also pass other args
4411
+ await show2(uid, ExtensionDetailReadme, x, y, {
4412
+ href,
4413
+ menuId: ExtensionDetailReadme,
4414
+ nodeName
4415
+ });
4416
+ // TODO
4417
+ return state;
4083
4418
  };
4084
4419
 
4085
- const getDescription = extension => {
4086
- if (!extension || !extension.description) {
4087
- return 'n/a';
4088
- }
4089
- return extension.description;
4420
+ const handleScroll = (state, scrollTop, scrollSource = Script) => {
4421
+ const newScrollTop = Math.max(0, scrollTop);
4422
+ return {
4423
+ ...state,
4424
+ readmeScrollTop: newScrollTop,
4425
+ scrollSource
4426
+ };
4090
4427
  };
4091
4428
 
4092
- const getName = extension => {
4093
- if (extension && extension.name) {
4094
- return extension.name;
4095
- }
4096
- if (extension && extension.id) {
4097
- return extension.id;
4098
- }
4099
- return 'n/a';
4429
+ const handleSelectionChange = async (state, selection) => {
4430
+ // console.log('selection change')
4431
+ return state;
4100
4432
  };
4101
4433
 
4102
- const getDownloadCount = extension => {
4103
- if (!extension) {
4104
- return 'n/a';
4105
- }
4106
-
4107
- // Check for download count in various possible locations
4108
- const downloadCount = extension.downloadCount || extension.downloads || extension.marketplace?.downloadCount || extension.marketplace?.downloads || extension.packageJSON?.downloadCount || extension.packageJSON?.downloads;
4109
- if (!downloadCount) {
4110
- return 'n/a';
4111
- }
4112
-
4113
- // Format the number with commas for better readability
4114
- return downloadCount.toLocaleString();
4434
+ const focusId = 451;
4435
+ const handleTabFocus = (state, name) => {
4436
+ const tabIndex = state.tabs.findIndex(tab => tab.name === name);
4437
+ const newFocusedTabIndex = tabIndex === -1 ? state.focusedTabIndex : tabIndex;
4438
+ return {
4439
+ ...state,
4440
+ focus: focusId,
4441
+ focusedTabIndex: newFocusedTabIndex
4442
+ };
4115
4443
  };
4116
4444
 
4117
- const getRating = extension => {
4118
- if (!extension) {
4119
- return 'n/a';
4120
- }
4121
-
4122
- // Check for rating in various possible locations
4123
- const rating = extension.rating || extension.averageRating || extension.marketplace?.rating || extension.marketplace?.averageRating || extension.packageJSON?.rating || extension.packageJSON?.averageRating;
4124
- if (!rating) {
4125
- return 'n/a';
4126
- }
4127
-
4128
- // Format rating to one decimal place
4129
- return rating.toFixed(1);
4445
+ const error = async error => {
4446
+ // TODO send message to error worker or log worker
4447
+ // @ts-ignore
4448
+ console.error(error);
4130
4449
  };
4131
4450
 
4132
- const getBadge = (builtin, badgeEnabled) => {
4133
- if (builtin && badgeEnabled) {
4134
- return 'builtin';
4451
+ const loadChangelogContent = async path => {
4452
+ try {
4453
+ const changelogUrl = join(path, 'CHANGELOG.md');
4454
+ const changelogContent = await readFile(changelogUrl);
4455
+ return changelogContent;
4456
+ } catch (error$1) {
4457
+ if (isEnoentError(error$1)) {
4458
+ return '';
4459
+ }
4460
+ await error(new VError(error$1, 'Failed to load Changelog content'));
4461
+ return `${error$1}`;
4135
4462
  }
4136
- return '';
4137
- };
4138
-
4139
- const hasColorThemes = extension => {
4140
- return Boolean(extension && extension.colorThemes && extension.colorThemes.length > 0);
4141
4463
  };
4142
4464
 
4143
- const loadHeaderContent = (state, platform, extension) => {
4465
+ const selectTabChangelog = async state => {
4144
4466
  const {
4145
- assetDir,
4146
- builtinExtensionsBadgeEnabled
4467
+ baseUrl,
4468
+ extension,
4469
+ locationProtocol,
4470
+ tabs
4147
4471
  } = state;
4148
- const iconSrc = getIcon(extension, platform, assetDir);
4149
- const description = getDescription(extension);
4150
- const name = getName(extension);
4151
- const extensionUri = extension.uri || extension.path;
4152
- const extensionId = extension?.id || 'n/a';
4153
- const extensionVersion = extension?.version || 'n/a';
4154
- const hasColorTheme = hasColorThemes(extension);
4155
- const isBuiltin = extension?.builtin;
4156
- const badge = getBadge(isBuiltin, builtinExtensionsBadgeEnabled);
4157
- const downloadCount = getDownloadCount(extension);
4158
- const rating = getRating(extension);
4472
+ const changelogContent = await loadChangelogContent(extension.path); // TODO use uri
4473
+ const changelogMarkdownHtml = await renderMarkdown(changelogContent, {
4474
+ baseUrl,
4475
+ locationProtocol
4476
+ });
4477
+ const changelogDom = await getMarkdownVirtualDom(changelogMarkdownHtml);
4478
+ const newTabs = tabs.map(tab => {
4479
+ return {
4480
+ ...tab,
4481
+ selected: tab.name === Changelog
4482
+ };
4483
+ });
4159
4484
  return {
4160
- badge,
4161
- description,
4162
- downloadCount,
4163
- extension,
4164
- extensionId,
4165
- extensionUri,
4166
- extensionVersion,
4167
- hasColorTheme,
4168
- iconSrc,
4169
- name,
4170
- rating
4485
+ ...state,
4486
+ changelogVirtualDom: changelogDom,
4487
+ selectedTab: Changelog,
4488
+ tabs: newTabs
4171
4489
  };
4172
4490
  };
4173
4491
 
4174
- const stringifyCategory = category => {
4175
- if (typeof category === 'string') {
4176
- return category;
4177
- }
4178
- return JSON.stringify(category);
4179
- };
4180
- const toCategory = categoryString => {
4181
- return {
4182
- id: categoryString.toLowerCase(),
4183
- label: categoryString
4184
- };
4185
- };
4186
- const getCategories = extension => {
4187
- if (!hasProperty(extension, 'categories') || !Array.isArray(extension.categories)) {
4188
- return [];
4189
- }
4190
- const categoryStrings = extension.categories.map(stringifyCategory);
4191
- const categories = categoryStrings.map(toCategory);
4192
- return categories;
4492
+ const selectTabDefault = async state => {
4493
+ return state;
4193
4494
  };
4194
4495
 
4195
- const BYTE_UNITS = ['B', 'kB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'];
4196
- const BIBYTE_UNITS = ['B', 'KiB', 'MiB', 'GiB', 'TiB', 'PiB', 'EiB', 'ZiB', 'YiB'];
4197
- const BIT_UNITS = ['b', 'kbit', 'Mbit', 'Gbit', 'Tbit', 'Pbit', 'Ebit', 'Zbit', 'Ybit'];
4198
- const BIBIT_UNITS = ['b', 'kibit', 'Mibit', 'Gibit', 'Tibit', 'Pibit', 'Eibit', 'Zibit', 'Yibit'];
4199
-
4200
- /*
4201
- Formats the given number using `Number#toLocaleString`.
4202
- - If locale is a string, the value is expected to be a locale-key (for example: `de`).
4203
- - If locale is true, the system default locale is used for translation.
4204
- - If no value for locale is specified, the number is returned unmodified.
4205
- */
4206
- const toLocaleString = (number, locale, options) => {
4207
- let result = number;
4208
- if (typeof locale === 'string' || Array.isArray(locale)) {
4209
- result = number.toLocaleString(locale, options);
4210
- } else if (locale === true || options !== undefined) {
4211
- result = number.toLocaleString(undefined, options);
4212
- }
4213
- return result;
4214
- };
4215
- const log10 = numberOrBigInt => {
4216
- if (typeof numberOrBigInt === 'number') {
4217
- return Math.log10(numberOrBigInt);
4218
- }
4219
- const string = numberOrBigInt.toString(10);
4220
- return string.length + Math.log10(`0.${string.slice(0, 15)}`);
4221
- };
4222
- const log = numberOrBigInt => {
4223
- if (typeof numberOrBigInt === 'number') {
4224
- return Math.log(numberOrBigInt);
4225
- }
4226
- return log10(numberOrBigInt) * Math.log(10);
4227
- };
4228
- const divide = (numberOrBigInt, divisor) => {
4229
- if (typeof numberOrBigInt === 'number') {
4230
- return numberOrBigInt / divisor;
4231
- }
4232
- const integerPart = numberOrBigInt / BigInt(divisor);
4233
- const remainder = numberOrBigInt % BigInt(divisor);
4234
- return Number(integerPart) + Number(remainder) / divisor;
4235
- };
4236
- const applyFixedWidth = (result, fixedWidth) => {
4237
- if (fixedWidth === undefined) {
4238
- return result;
4239
- }
4240
- if (typeof fixedWidth !== 'number' || !Number.isSafeInteger(fixedWidth) || fixedWidth < 0) {
4241
- throw new TypeError(`Expected fixedWidth to be a non-negative integer, got ${typeof fixedWidth}: ${fixedWidth}`);
4242
- }
4243
- if (fixedWidth === 0) {
4244
- return result;
4245
- }
4246
- return result.length < fixedWidth ? result.padStart(fixedWidth, ' ') : result;
4247
- };
4248
- const buildLocaleOptions = options => {
4496
+ const selectTabDetails = async state => {
4249
4497
  const {
4250
- minimumFractionDigits,
4251
- maximumFractionDigits
4252
- } = options;
4253
- if (minimumFractionDigits === undefined && maximumFractionDigits === undefined) {
4254
- return undefined;
4255
- }
4498
+ baseUrl,
4499
+ locationProtocol,
4500
+ readmeUrl,
4501
+ tabs
4502
+ } = state;
4503
+ const readmeContent = await loadReadmeContent(readmeUrl);
4504
+ const readmeHtml = await renderMarkdown(readmeContent, {
4505
+ baseUrl,
4506
+ linksExternal: true,
4507
+ locationProtocol
4508
+ });
4509
+ const detailsDom = await getMarkdownVirtualDom(readmeHtml);
4510
+ const newTabs = tabs.map(tab => {
4511
+ return {
4512
+ ...tab,
4513
+ selected: tab.name === Details
4514
+ };
4515
+ });
4256
4516
  return {
4257
- ...(minimumFractionDigits !== undefined && {
4258
- minimumFractionDigits
4259
- }),
4260
- ...(maximumFractionDigits !== undefined && {
4261
- maximumFractionDigits
4262
- }),
4263
- roundingMode: 'trunc'
4517
+ ...state,
4518
+ detailsVirtualDom: detailsDom,
4519
+ selectedTab: Details,
4520
+ tabs: newTabs
4264
4521
  };
4265
4522
  };
4266
- function prettyBytes(number, options) {
4267
- if (typeof number !== 'bigint' && !Number.isFinite(number)) {
4268
- throw new TypeError(`Expected a finite number, got ${typeof number}: ${number}`);
4269
- }
4270
- options = {
4271
- bits: false,
4272
- binary: false,
4273
- space: true,
4274
- nonBreakingSpace: false,
4275
- ...options
4276
- };
4277
- const UNITS = options.bits ? options.binary ? BIBIT_UNITS : BIT_UNITS : options.binary ? BIBYTE_UNITS : BYTE_UNITS;
4278
- const separator = options.space ? options.nonBreakingSpace ? '\u00A0' : ' ' : '';
4279
4523
 
4280
- // Handle signed zero case
4281
- const isZero = typeof number === 'number' ? number === 0 : number === 0n;
4282
- if (options.signed && isZero) {
4283
- const result = ` 0${separator}${UNITS[0]}`;
4284
- return applyFixedWidth(result, options.fixedWidth);
4285
- }
4286
- const isNegative = number < 0;
4287
- const prefix = isNegative ? '-' : options.signed ? '+' : '';
4288
- if (isNegative) {
4289
- number = -number;
4290
- }
4291
- const localeOptions = buildLocaleOptions(options);
4292
- let result;
4293
- if (number < 1) {
4294
- const numberString = toLocaleString(number, options.locale, localeOptions);
4295
- result = prefix + numberString + separator + UNITS[0];
4296
- } else {
4297
- const exponent = Math.min(Math.floor(options.binary ? log(number) / Math.log(1024) : log10(number) / 3), UNITS.length - 1);
4298
- number = divide(number, (options.binary ? 1024 : 1000) ** exponent);
4299
- if (!localeOptions) {
4300
- const minPrecision = Math.max(3, Math.floor(number).toString().length);
4301
- number = number.toPrecision(minPrecision);
4302
- }
4303
- const numberString = toLocaleString(Number(number), options.locale, localeOptions);
4304
- const unit = UNITS[exponent];
4305
- result = prefix + numberString + separator + unit;
4524
+ const selectTabFeatures = async state => {
4525
+ const {
4526
+ baseUrl,
4527
+ extension,
4528
+ features,
4529
+ locationProtocol,
4530
+ selectedFeature,
4531
+ tabs
4532
+ } = state;
4533
+ if (features.length === 0) {
4534
+ return state;
4306
4535
  }
4307
- return applyFixedWidth(result, options.fixedWidth);
4308
- }
4309
-
4310
- const getDisplaySize = size => {
4311
- return prettyBytes(size, {
4312
- maximumFractionDigits: 1
4536
+ const actualSelectedFeature = selectedFeature || Theme;
4537
+ const fn = getFeatureDetailsHandler(actualSelectedFeature);
4538
+ const partialNewState = await fn(extension, baseUrl, locationProtocol);
4539
+ const newTabs = tabs.map(tab => {
4540
+ return {
4541
+ ...tab,
4542
+ selected: tab.name === Features
4543
+ };
4313
4544
  });
4314
- };
4315
-
4316
- const supportsFileSize = uri => {
4317
- if (uri.startsWith('http:') || uri.startsWith('https://')) {
4318
- return false;
4319
- }
4320
- return true;
4321
- };
4322
- const getFolderSize = async uri => {
4323
- if (!uri) {
4324
- throw new VError(`uri is required`);
4325
- }
4326
- if (!supportsFileSize(uri)) {
4327
- return 0;
4328
- }
4329
- try {
4330
- // @ts-ignore
4331
- return await invoke('FileSystem.getFolderSize', uri);
4332
- } catch {
4333
- return 0;
4334
- }
4335
- };
4336
-
4337
- const getMarketplaceEntries = isBuiltin => {
4338
- if (isBuiltin) {
4339
- return [];
4340
- }
4341
- return [{
4342
- key: published(),
4343
- odd: true,
4344
- value: 'n/a'
4345
- }, {
4346
- key: lastReleased(),
4347
- value: 'n/a'
4348
- }];
4349
- };
4350
-
4351
- const getLicenseLink = extension => {
4352
- // TODO
4353
- return '#';
4354
- };
4355
-
4356
- const ensureValidLink = link => {
4357
- if (!link) {
4358
- return '';
4359
- }
4360
- try {
4361
- const parsed = new URL(link);
4362
- if (parsed.protocol !== 'https:') {
4363
- return '';
4545
+ const newFeatures = features.map(feature => {
4546
+ if (feature.id === actualSelectedFeature) {
4547
+ return {
4548
+ ...feature,
4549
+ selected: true
4550
+ };
4364
4551
  }
4365
- return link;
4366
- } catch {
4367
- return '';
4368
- }
4369
- };
4370
-
4371
- const getRepositoryLinkRaw = extension => {
4372
- if (extension && hasProperty(extension, 'repository') && typeof extension.repository === 'string') {
4373
- return extension.repository; // TODO watch out for javascript: or other invalid links or path traversal
4374
- }
4375
- return '';
4376
- };
4377
- const getRepositoryLink = extension => {
4378
- const raw = getRepositoryLinkRaw(extension);
4379
- const validLink = ensureValidLink(raw);
4380
- return validLink;
4381
- };
4382
- const getIssuesLink = extension => {
4383
- const repositoryLink = getRepositoryLink(extension);
4384
- if (!repositoryLink) {
4385
- return '';
4386
- }
4387
- if (repositoryLink && repositoryLink.startsWith('https://github.com')) {
4388
- return `${repositoryLink}/issues`;
4389
- }
4390
- return '';
4552
+ return {
4553
+ ...feature,
4554
+ selected: false
4555
+ };
4556
+ });
4557
+ return {
4558
+ ...state,
4559
+ ...partialNewState,
4560
+ features: newFeatures,
4561
+ selectedFeature: features[0].id || '',
4562
+ selectedTab: Features,
4563
+ tabs: newTabs
4564
+ };
4391
4565
  };
4392
4566
 
4393
- const getResources = (isBuiltin, extension) => {
4394
- if (isBuiltin) {
4395
- return [];
4396
- }
4397
- const repositoryLink = getRepositoryLink(extension);
4398
- const issueLink = getIssuesLink(extension);
4399
- const licenseLink = getLicenseLink();
4400
- // TODO
4401
- return [{
4402
- icon: 'LinkExternal',
4403
- label: marketplace(),
4404
- url: '#'
4405
- }, {
4406
- icon: 'LinkExternal',
4407
- label: issues(),
4408
- url: issueLink
4409
- }, {
4410
- icon: 'Repo',
4411
- label: repository(),
4412
- url: repositoryLink
4413
- }, {
4414
- icon: 'LinkExternal',
4415
- label: license(),
4416
- url: licenseLink
4417
- }];
4567
+ const getSelectTabHandler = name => {
4568
+ switch (name) {
4569
+ case Changelog:
4570
+ return selectTabChangelog;
4571
+ case Details:
4572
+ return selectTabDetails;
4573
+ case Features:
4574
+ return selectTabFeatures;
4575
+ default:
4576
+ return selectTabDefault;
4577
+ }
4418
4578
  };
4419
4579
 
4420
- const loadSideBarContent = async (extensionId, extensionVersion, extensionUri, isBuiltin, extension, showSizeLink) => {
4421
- const folderSize = await getFolderSize(extensionUri);
4422
- const displaySize = getDisplaySize(folderSize);
4423
- const installationEntries = getInstallationEntries(displaySize, extensionId, extensionVersion, extensionUri, showSizeLink);
4424
- const marketplaceEntries = getMarketplaceEntries(isBuiltin);
4425
- const categories = getCategories(extension);
4426
- const resources = getResources(isBuiltin, extension);
4427
- return {
4428
- categories,
4580
+ const selectTab = (state, name) => {
4581
+ const fn = getSelectTabHandler(name);
4582
+ return fn(state);
4583
+ };
4584
+
4585
+ const handleTabsClick = (state, name) => {
4586
+ return selectTab(state, name);
4587
+ };
4588
+
4589
+ const hideSizeLink = state => {
4590
+ const {
4429
4591
  displaySize,
4430
- folderSize,
4592
+ extensionId,
4593
+ extensionUri,
4594
+ extensionVersion
4595
+ } = state;
4596
+ const newShowSizeLink = false;
4597
+ const installationEntries = getInstallationEntries(displaySize, extensionId, extensionVersion, extensionUri, newShowSizeLink);
4598
+ return {
4599
+ ...state,
4431
4600
  installationEntries,
4432
- marketplaceEntries,
4433
- resources
4601
+ showSizeLink: newShowSizeLink
4434
4602
  };
4435
4603
  };
4436
4604
 
4437
- const getSavedChangelogScrollTop = savedState => {
4438
- if (savedState && typeof savedState === 'object' && 'changelogScrollTop' in savedState && typeof savedState.changelogScrollTop === 'number') {
4439
- return savedState.changelogScrollTop;
4605
+ const sendMessagePortToExtensionHostWorker = async port => {
4606
+ await sendMessagePortToExtensionHostWorker$1(port, 0);
4607
+ };
4608
+
4609
+ const createExtensionHostWorkerRpc = async () => {
4610
+ try {
4611
+ const rpc = await TransferMessagePortRpcParent.create({
4612
+ commandMap: {},
4613
+ send: sendMessagePortToExtensionHostWorker
4614
+ });
4615
+ return rpc;
4616
+ } catch (error) {
4617
+ throw new VError(error, `Failed to create extension host rpc`);
4440
4618
  }
4441
- return 0;
4442
4619
  };
4443
4620
 
4444
- const getSavedReadmeScrollTop = savedState => {
4445
- if (savedState && typeof savedState === 'object' && 'readmeScrollTop' in savedState && typeof savedState.readmeScrollTop === 'number') {
4446
- return savedState.readmeScrollTop;
4621
+ const initializeExtensionHostWorker = async () => {
4622
+ const rpc = await createExtensionHostWorkerRpc();
4623
+ set$4(rpc);
4624
+ };
4625
+
4626
+ const createExtensionManagementWorkerRpc = async () => {
4627
+ try {
4628
+ const rpc = await TransferMessagePortRpcParent.create({
4629
+ commandMap: {},
4630
+ send: port => sendMessagePortToExtensionManagementWorker(port, 0)
4631
+ });
4632
+ return rpc;
4633
+ } catch (error) {
4634
+ throw new VError(error, `Failed to create extension management rpc`);
4447
4635
  }
4448
- return 0;
4449
4636
  };
4450
4637
 
4451
- const getSavedSelectedFeature = savedState => {
4452
- if (savedState && typeof savedState === 'object' && 'selectedFeature' in savedState && typeof savedState.selectedFeature === 'string') {
4453
- return savedState.selectedFeature;
4638
+ const initializeExtensionManagementWorker = async () => {
4639
+ try {
4640
+ const rpc = await createExtensionManagementWorkerRpc();
4641
+ set$8(rpc);
4642
+ } catch {
4643
+ // ignore
4454
4644
  }
4455
- return Details;
4456
4645
  };
4457
4646
 
4458
- const getSavedSelectedTab = savedState => {
4459
- if (savedState && typeof savedState === 'object' && 'selectedTab' in savedState && typeof savedState.selectedTab === 'string') {
4460
- return savedState.selectedTab;
4647
+ const sendMessagePortToFileSystemWorker = async port => {
4648
+ await sendMessagePortToFileSystemWorker$1(port, 0);
4649
+ };
4650
+
4651
+ const createFileSystemWorkerRpc = async () => {
4652
+ try {
4653
+ const rpc = await TransferMessagePortRpcParent.create({
4654
+ commandMap: {},
4655
+ send: sendMessagePortToFileSystemWorker
4656
+ });
4657
+ return rpc;
4658
+ } catch (error) {
4659
+ throw new VError(error, `Failed to create file system worker rpc`);
4461
4660
  }
4462
- return Details;
4463
4661
  };
4464
4662
 
4465
- const restoreState = savedState => {
4466
- const selectedTab = getSavedSelectedTab(savedState);
4467
- const selectedFeature = getSavedSelectedFeature(savedState);
4468
- const readmeScrollTop = getSavedReadmeScrollTop(savedState);
4469
- const changelogScrollTop = getSavedChangelogScrollTop(savedState);
4470
- return {
4471
- changelogScrollTop,
4472
- readmeScrollTop,
4473
- selectedFeature,
4474
- selectedTab
4475
- };
4663
+ const initializeFileSystemWorker = async () => {
4664
+ const rpc = await createFileSystemWorkerRpc();
4665
+ set$1(rpc);
4476
4666
  };
4477
4667
 
4478
- const isEnabled = tab => {
4479
- return tab.enabled;
4668
+ const sendMessagePortToMarkdownWorker = async port => {
4669
+ await sendMessagePortToMarkdownWorker$1(port, 0);
4480
4670
  };
4481
- const loadContent = async (state, platform, savedState, isTest = false) => {
4482
- if (isTest) {
4483
- savedState = undefined;
4484
- }
4485
- const {
4486
- uri,
4487
- width
4488
- } = state;
4489
- const id = getExtensionIdFromUri(uri);
4490
- const extension = await getExtension(id, platform);
4491
- if (!extension) {
4492
- throw new ExtensionNotFoundError(id);
4671
+
4672
+ const createMarkdownWorkerRpc = async () => {
4673
+ try {
4674
+ const rpc = await TransferMessagePortRpcParent.create({
4675
+ commandMap: {},
4676
+ send: sendMessagePortToMarkdownWorker
4677
+ });
4678
+ return rpc;
4679
+ } catch (error) {
4680
+ throw new VError(error, `Failed to create markdown worker rpc`);
4493
4681
  }
4494
- const commit = await getCommit();
4495
- const headerData = loadHeaderContent(state, platform, extension);
4496
- const {
4497
- badge,
4498
- description,
4499
- downloadCount,
4500
- extensionId,
4501
- extensionUri,
4502
- extensionVersion,
4503
- hasColorTheme,
4504
- iconSrc,
4505
- name,
4506
- rating
4507
- } = headerData;
4508
- const readmeUrl = join(extensionUri, 'README.md');
4509
- const changelogUrl = join(extensionUri, 'CHANGELOG.md');
4510
- const [hasReadme, hasChangelog] = await Promise.all([existsFile(readmeUrl), existsFile(changelogUrl)]);
4511
- const readmeContent = hasReadme ? await loadReadmeContent(readmeUrl) : noReadmeFound();
4512
- const baseUrl = getBaseUrl(extension.path, platform);
4513
- const locationProtocol = location.protocol;
4514
- const locationHost = location.host;
4515
- const readmeHtml = await renderMarkdown(readmeContent, {
4516
- baseUrl,
4517
- commit,
4518
- linksExternal: true,
4519
- locationProtocol
4520
- });
4521
- const detailsVirtualDom = await getMarkdownVirtualDom(readmeHtml, {
4522
- scrollToTopEnabled: true
4523
- });
4524
- const isBuiltin = extension?.isBuiltin;
4525
- const disabled = extension?.disabled;
4526
- const buttons = getExtensionDetailButtons(hasColorTheme, isBuiltin, disabled);
4527
- const size = getViewletSize(width);
4528
- const {
4529
- changelogScrollTop,
4530
- readmeScrollTop,
4531
- selectedFeature,
4532
- selectedTab
4533
- } = restoreState(savedState);
4534
- const features = getFeatures(selectedFeature || Theme, extension);
4535
- const hasFeatures = features.length > 0;
4536
- const tabs = getTabs(selectedTab, hasReadme, hasFeatures, hasChangelog);
4537
- const enabledTabs = tabs.filter(isEnabled);
4538
- const sizeValue = getViewletSize(width || 0);
4539
- const showSizeLink = platform !== Web$1;
4540
- const {
4541
- categories,
4542
- displaySize,
4543
- folderSize,
4544
- installationEntries,
4545
- marketplaceEntries,
4546
- resources
4547
- } = await loadSideBarContent(extensionId, extensionVersion, extensionUri, isBuiltin, extension, showSizeLink);
4548
- const padding = getPadding(width);
4549
- const sideBarWidth = getSideBarWidth(width);
4550
- const showSideBar = sideBarWidth > 0;
4551
- return {
4552
- ...state,
4553
- badge,
4554
- baseUrl,
4555
- buttons,
4556
- categories,
4557
- changelogScrollTop,
4558
- commit,
4559
- description,
4560
- detailsVirtualDom,
4561
- disabled,
4562
- displaySize,
4563
- downloadCount,
4564
- extension,
4565
- extensionId,
4566
- extensionUri,
4567
- extensionVersion,
4568
- features,
4569
- folderSize,
4570
- hasColorTheme,
4571
- hasReadme,
4572
- iconSrc,
4573
- installationEntries,
4574
- locationHost,
4575
- locationProtocol,
4576
- marketplaceEntries,
4577
- name,
4578
- paddingLeft: padding,
4579
- paddingRight: padding,
4580
- rating,
4581
- readmeScrollTop,
4582
- readmeUrl,
4583
- resources,
4584
- scrollSource: Script,
4585
- scrollToTopButtonEnabled: true,
4586
- selectedTab,
4587
- showSideBar,
4588
- showSizeLink,
4589
- sideBarWidth,
4590
- sizeOnDisk: size,
4591
- sizeValue,
4592
- tabs: enabledTabs
4593
- };
4682
+ };
4683
+
4684
+ const initializeMarkdownWorker = async () => {
4685
+ const rpc = await createMarkdownWorkerRpc();
4686
+ set$3(rpc);
4687
+ };
4688
+
4689
+ const initialize = async () => {
4690
+ // TODO load markdown worker only when needed
4691
+ await Promise.all([initializeMarkdownWorker(), initializeFileSystemWorker(), initializeExtensionHostWorker(), initializeExtensionManagementWorker()]);
4594
4692
  };
4595
4693
 
4596
4694
  const loadContent2 = async (state, savedState, isTest = false) => {
@@ -4828,17 +4926,19 @@ const getAdditionalDetailsVirtualDom = (showAdditionalDetails, firstHeading, ent
4828
4926
  if (!showAdditionalDetails) {
4829
4927
  return [];
4830
4928
  }
4929
+ const sections = [...getAdditionalDetailsEntryVirtualDom(firstHeading, entries, getMoreInfoVirtualDom), ...(secondEntries.length > 0 ? getAdditionalDetailsEntryVirtualDom(secondHeading, secondEntries, getMoreInfoVirtualDom) : []), ...getAdditionalDetailsEntryVirtualDom(thirdHeading, categories, getCategoriesDom), ...getAdditionalDetailsEntryVirtualDom(fourthHeading, resources, getResourcesVirtualDom)];
4930
+ const childCount = secondEntries.length > 0 ? 4 : 3;
4831
4931
  return [{
4832
4932
  childCount: 1,
4833
4933
  className: Aside,
4834
4934
  type: Aside$1
4835
4935
  }, {
4836
- childCount: 4,
4936
+ childCount: childCount,
4837
4937
  className: AdditionalDetails,
4838
4938
  onContextMenu: HandleAdditionalDetailContextMenu,
4839
4939
  tabIndex: 0,
4840
4940
  type: Div
4841
- }, ...getAdditionalDetailsEntryVirtualDom(firstHeading, entries, getMoreInfoVirtualDom), ...getAdditionalDetailsEntryVirtualDom(secondHeading, secondEntries, getMoreInfoVirtualDom), ...getAdditionalDetailsEntryVirtualDom(thirdHeading, categories, getCategoriesDom), ...getAdditionalDetailsEntryVirtualDom(fourthHeading, resources, getResourcesVirtualDom)];
4941
+ }, ...sections];
4842
4942
  };
4843
4943
 
4844
4944
  const getNoReadmeVirtualDom = () => {
@@ -4947,10 +5047,11 @@ const getExtensionDetailDescriptionVirtualDom = description => {
4947
5047
  }, text(description)];
4948
5048
  };
4949
5049
 
5050
+ const className = mergeClassNames(Button, ButtonPrimary);
4950
5051
  const getButtonVirtualDom = (message, onClick, name) => {
4951
5052
  return [{
4952
5053
  childCount: 1,
4953
- className: mergeClassNames(Button, ButtonPrimary),
5054
+ className,
4954
5055
  name,
4955
5056
  onClick,
4956
5057
  type: Button$1
@@ -5038,7 +5139,7 @@ const getTabClassName = isSelected => {
5038
5139
  return isSelected ? selectedClassName : defaultClassName;
5039
5140
  };
5040
5141
 
5041
- const getTabVirtualDom = tab => {
5142
+ const getTabVirtualDom = (tab, tabIndex, focusedTabIndex) => {
5042
5143
  const {
5043
5144
  label,
5044
5145
  name,
@@ -5046,26 +5147,27 @@ const getTabVirtualDom = tab => {
5046
5147
  } = tab;
5047
5148
  const className = getTabClassName(selected);
5048
5149
  const ariaSelected = selected;
5150
+ const tabIndexValue = tabIndex === focusedTabIndex ? 0 : -1;
5049
5151
  return [{
5050
5152
  ariaSelected,
5051
5153
  childCount: 1,
5052
5154
  className,
5053
5155
  name,
5156
+ onFocus: HandleTabFocus,
5054
5157
  role: Tab,
5055
- tabIndex: -1,
5158
+ tabIndex: tabIndexValue,
5056
5159
  type: Button$1
5057
5160
  }, text(label)];
5058
5161
  };
5059
5162
 
5060
- const getTabsVirtualDom = tabs => {
5163
+ const getTabsVirtualDom = (tabs, focusedTabIndex) => {
5061
5164
  return [{
5062
5165
  childCount: tabs.length,
5063
5166
  className: ExtensionDetailTabs,
5064
5167
  onClick: HandleTabsClick,
5065
5168
  role: TabList,
5066
- tabIndex: 0,
5067
5169
  type: Div
5068
- }, ...tabs.flatMap(getTabVirtualDom)];
5170
+ }, ...tabs.flatMap((tab, index) => getTabVirtualDom(tab, index, focusedTabIndex))];
5069
5171
  };
5070
5172
 
5071
5173
  const getClassNames = size => {
@@ -5094,6 +5196,7 @@ const getExtensionDetailVirtualDom = (newState, selectedTab) => {
5094
5196
  changelogVirtualDom,
5095
5197
  description,
5096
5198
  detailsVirtualDom,
5199
+ focusedTabIndex,
5097
5200
  iconSrc,
5098
5201
  name,
5099
5202
  resources,
@@ -5109,7 +5212,7 @@ const getExtensionDetailVirtualDom = (newState, selectedTab) => {
5109
5212
  childCount: 3,
5110
5213
  className: mergeClassNames(Viewlet, ExtensionDetail, sizeClass),
5111
5214
  type: Div
5112
- }, ...getExtensionDetailHeaderVirtualDom(name, iconSrc, description, badge, buttons, settingsButtonEnabled), ...getTabsVirtualDom(tabs), ...getExtensionDetailContentVirtualDom(detailsVirtualDom, selectedTab, width, scrollToTopButtonEnabled, categories, resources, showAdditionalDetailsBreakpoint, changelogVirtualDom, newState)];
5215
+ }, ...getExtensionDetailHeaderVirtualDom(name, iconSrc, description, badge, buttons, settingsButtonEnabled), ...getTabsVirtualDom(tabs, focusedTabIndex), ...getExtensionDetailContentVirtualDom(detailsVirtualDom, selectedTab, width, scrollToTopButtonEnabled, categories, resources, showAdditionalDetailsBreakpoint, changelogVirtualDom, newState)];
5113
5216
  return dom;
5114
5217
  };
5115
5218
 
@@ -5119,7 +5222,31 @@ const renderDom = (oldState, newState) => {
5119
5222
  };
5120
5223
 
5121
5224
  const renderFocus = (oldState, newState) => {
5122
- return ['Viewlet.focusElementByName', ''];
5225
+ const {
5226
+ focus,
5227
+ focusedTabIndex,
5228
+ tabs,
5229
+ uid
5230
+ } = newState;
5231
+ if (focus === 451) {
5232
+ const {
5233
+ name
5234
+ } = tabs[focusedTabIndex];
5235
+ return [FocusElementByName, uid, name];
5236
+ }
5237
+ return [FocusElementByName, ''];
5238
+ };
5239
+
5240
+ const renderFocusContext = (oldState, newState) => {
5241
+ const {
5242
+ focus,
5243
+ uid
5244
+ } = newState;
5245
+ if (focus === 451) {
5246
+ return [SetFocusContext, uid, focus];
5247
+ }
5248
+ // TODO set focus context
5249
+ return [FocusElementByName, ''];
5123
5250
  };
5124
5251
 
5125
5252
  const getScrollTop = (selectedTab, readmeScrollTop, changelogScrollTop) => {
@@ -5153,6 +5280,8 @@ const getRenderer = diffType => {
5153
5280
  return renderCss;
5154
5281
  case RenderFocus:
5155
5282
  return renderFocus;
5283
+ case RenderFocusContext:
5284
+ return renderFocusContext;
5156
5285
  case RenderItems:
5157
5286
  return renderDom;
5158
5287
  case RenderScrollTop:
@@ -5218,6 +5347,9 @@ const renderEventListeners = () => {
5218
5347
  }, {
5219
5348
  name: HandleClickDisable,
5220
5349
  params: ['handleClickDisable']
5350
+ }, {
5351
+ name: HandleClickEnable,
5352
+ params: ['handleClickEnable']
5221
5353
  }, {
5222
5354
  name: HandleClickScrollToTop,
5223
5355
  params: ['handleClickScrollToTop'],
@@ -5238,6 +5370,9 @@ const renderEventListeners = () => {
5238
5370
  }, {
5239
5371
  name: HandleSelectionChange,
5240
5372
  params: ['handleSelectionChange']
5373
+ }, {
5374
+ name: HandleTabFocus,
5375
+ params: ['handleTabFocus', TargetName]
5241
5376
  }];
5242
5377
  };
5243
5378
 
@@ -5278,7 +5413,10 @@ const commandMap = {
5278
5413
  'ExtensionDetail.diff2': diff2,
5279
5414
  'ExtensionDetail.dispose': dispose,
5280
5415
  'ExtensionDetail.executeCopy': wrapCommand(executeCopy),
5416
+ 'ExtensionDetail.focusNextTab': wrapCommand(focusNextTab),
5417
+ 'ExtensionDetail.focusPreviousTab': wrapCommand(focusPreviousTab),
5281
5418
  'ExtensionDetail.getCommandIds': getCommandIds,
5419
+ 'ExtensionDetail.getKeyBindings': getKeyBindings,
5282
5420
  'ExtensionDetail.getMenuEntries': getMenuEntries,
5283
5421
  'ExtensionDetail.getMenuEntries2': wrapGetter(getMenuEntries2),
5284
5422
  'ExtensionDetail.getMenuIds': getMenuIds,
@@ -5292,6 +5430,7 @@ const commandMap = {
5292
5430
  'ExtensionDetail.handleClickSettings': wrapCommand(handleClickSettings),
5293
5431
  'ExtensionDetail.handleClickSize': wrapCommand(handleClickSize),
5294
5432
  'ExtensionDetail.handleClickUninstall': wrapCommand(handleClickUninstall),
5433
+ 'ExtensionDetail.handleExtensionsChanged': wrapCommand(handleExtensionsChanged),
5295
5434
  'ExtensionDetail.handleExtensionsStatusUpdate': wrapCommand(handleExtensionsStatusUpdate),
5296
5435
  'ExtensionDetail.handleFeaturesClick': wrapCommand(handleClickFeatures),
5297
5436
  'ExtensionDetail.handleIconError': wrapCommand(handleIconError),
@@ -5300,6 +5439,7 @@ const commandMap = {
5300
5439
  'ExtensionDetail.handleReadmeContextMenu': wrapCommand(handleReadmeContextMenu),
5301
5440
  'ExtensionDetail.handleScroll': wrapCommand(handleScroll),
5302
5441
  'ExtensionDetail.handleSelectionChange': wrapCommand(handleSelectionChange),
5442
+ 'ExtensionDetail.handleTabFocus': wrapCommand(handleTabFocus),
5303
5443
  'ExtensionDetail.handleTabsClick': wrapCommand(handleTabsClick),
5304
5444
  'ExtensionDetail.handleWheel': wrapCommand(handleScroll),
5305
5445
  // deprecated