@lvce-editor/extension-search-view 7.21.0 → 7.22.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.
@@ -2908,2039 +2908,2044 @@ const getComponentState = uid => {
2908
2908
  return get(uid).newState;
2909
2909
  };
2910
2910
 
2911
- const Tab = 2;
2912
- const Enter = 3;
2913
- const Escape = 8;
2914
- const Space = 9;
2915
- const PageUp = 10;
2916
- const PageDown = 11;
2917
- const End = 255;
2918
- const Home = 12;
2919
- const UpArrow = 14;
2920
- const DownArrow = 16;
2911
+ const mergeClassNames = (...classNames) => {
2912
+ return classNames.filter(Boolean).join(' ');
2913
+ };
2921
2914
 
2922
- const CtrlCmd = 1 << 11 >>> 0;
2915
+ const text = data => {
2916
+ return {
2917
+ childCount: 0,
2918
+ text: data,
2919
+ type: Text
2920
+ };
2921
+ };
2923
2922
 
2924
- const FocusExtensions = 15;
2925
- const FocusExtensionsInput = 7000;
2923
+ new Set(Object.values(VirtualDomElements));
2926
2924
 
2927
- const getKeyBindings = () => {
2928
- return [{
2929
- command: 'Extensions.closeSuggest',
2930
- key: Escape,
2931
- when: FocusExtensionsInput
2932
- }, {
2933
- command: 'Extensions.acceptCompletion',
2934
- key: Enter,
2935
- when: FocusExtensionsInput
2936
- }, {
2937
- command: 'Extensions.acceptCompletion',
2938
- key: Tab,
2939
- when: FocusExtensionsInput
2940
- }, {
2941
- command: 'Extensions.selectPreviousCompletion',
2942
- key: UpArrow,
2943
- when: FocusExtensionsInput
2944
- }, {
2945
- command: 'Extensions.selectNextCompletion',
2946
- key: DownArrow,
2947
- when: FocusExtensionsInput
2948
- }, {
2949
- command: 'Extensions.openSuggest',
2950
- key: CtrlCmd | Space,
2951
- when: FocusExtensionsInput
2952
- }, {
2953
- command: 'Extensions.focusFirst',
2954
- key: Home,
2955
- when: FocusExtensions
2956
- }, {
2957
- command: 'Extensions.focusLast',
2958
- key: End,
2959
- when: FocusExtensions
2960
- }, {
2961
- command: 'Extensions.focusPreviousPage',
2962
- key: PageUp,
2963
- when: FocusExtensions
2964
- }, {
2965
- command: 'Extensions.focusNextPage',
2966
- key: PageDown,
2967
- when: FocusExtensions
2968
- }, {
2969
- command: 'Extensions.focusPrevious',
2970
- key: UpArrow,
2971
- when: FocusExtensions
2972
- }, {
2973
- command: 'Extensions.focusNext',
2974
- key: DownArrow,
2975
- when: FocusExtensions
2976
- }, {
2977
- command: 'Extensions.handleClickCurrentButKeepFocus',
2978
- key: Space,
2979
- when: FocusExtensions
2980
- }, {
2981
- command: 'Extensions.handleClickCurrent',
2982
- key: Enter,
2983
- when: FocusExtensions
2984
- }, {
2985
- command: 'Extensions.toggleSuggest',
2986
- key: CtrlCmd | Space,
2987
- when: FocusExtensions
2988
- }, {
2989
- command: 'Extensions.scrollDown',
2990
- key: CtrlCmd | DownArrow,
2991
- when: FocusExtensions
2992
- }];
2925
+ const SetText = 1;
2926
+ const Replace = 2;
2927
+ const SetAttribute = 3;
2928
+ const RemoveAttribute = 4;
2929
+ const Add = 6;
2930
+ const NavigateChild = 7;
2931
+ const NavigateParent = 8;
2932
+ const RemoveChild = 9;
2933
+ const NavigateSibling = 10;
2934
+ const SetReferenceNodeUid = 11;
2935
+
2936
+ const isKey = key => {
2937
+ return key !== 'type' && key !== 'childCount';
2993
2938
  };
2994
2939
 
2995
- const Separator = 1;
2996
- const None$1 = 0;
2997
- const SubMenu = 4;
2998
- const Disabled = 5;
2940
+ const getKeys = node => {
2941
+ const keys = Object.keys(node).filter(isKey);
2942
+ return keys;
2943
+ };
2999
2944
 
3000
- const nonEnableableStatuses = [Installing, NotInstalled, Uninstalling];
3001
- const getEnablementFlags = (disabled, status) => {
3002
- if (status && nonEnableableStatuses.includes(status)) {
2945
+ const arrayToTree = nodes => {
2946
+ const result = [];
2947
+ let i = 0;
2948
+ while (i < nodes.length) {
2949
+ const node = nodes[i];
2950
+ const {
2951
+ children,
2952
+ nodesConsumed
2953
+ } = getChildrenWithCount(nodes, i + 1, node.childCount || 0);
2954
+ result.push({
2955
+ node,
2956
+ children
2957
+ });
2958
+ i += 1 + nodesConsumed;
2959
+ }
2960
+ return result;
2961
+ };
2962
+ const getChildrenWithCount = (nodes, startIndex, childCount) => {
2963
+ if (childCount === 0) {
3003
2964
  return {
3004
- disable: Disabled,
3005
- enable: Disabled
2965
+ children: [],
2966
+ nodesConsumed: 0
3006
2967
  };
3007
2968
  }
3008
- const isDisabled = status === Disabled$1 || status === undefined && disabled;
2969
+ const children = [];
2970
+ let i = startIndex;
2971
+ let remaining = childCount;
2972
+ let totalConsumed = 0;
2973
+ while (remaining > 0 && i < nodes.length) {
2974
+ const node = nodes[i];
2975
+ const nodeChildCount = node.childCount || 0;
2976
+ const {
2977
+ children: nodeChildren,
2978
+ nodesConsumed
2979
+ } = getChildrenWithCount(nodes, i + 1, nodeChildCount);
2980
+ children.push({
2981
+ node,
2982
+ children: nodeChildren
2983
+ });
2984
+ const nodeSize = 1 + nodesConsumed;
2985
+ i += nodeSize;
2986
+ totalConsumed += nodeSize;
2987
+ remaining--;
2988
+ }
3009
2989
  return {
3010
- disable: isDisabled ? Disabled : None$1,
3011
- enable: isDisabled ? None$1 : Disabled
2990
+ children,
2991
+ nodesConsumed: totalConsumed
3012
2992
  };
3013
2993
  };
3014
- const getMenuEntriesList = (builtin, disabled = false, status) => {
3015
- const enablementFlags = getEnablementFlags(disabled, status);
3016
- return [{
3017
- command: 'Extensions.enable',
3018
- flags: enablementFlags.enable,
3019
- id: 'enable',
3020
- label: enable$2()
3021
- }, {
3022
- command: 'Extensions.enableWorkspace',
3023
- flags: enablementFlags.enable,
3024
- id: 'enableWorkspace',
3025
- label: enableWorkspace$1()
3026
- }, {
3027
- command: '',
3028
- flags: Separator,
3029
- id: 'separator1',
3030
- label: ''
3031
- }, {
3032
- command: 'Extensions.disable',
3033
- flags: enablementFlags.disable,
3034
- id: 'disable',
3035
- label: disable$2()
3036
- }, {
3037
- command: 'Extensions.disableWorkspace',
3038
- flags: enablementFlags.disable,
3039
- id: 'disableWorkspace',
3040
- label: disableWorkspace$1()
3041
- }, {
3042
- command: '',
3043
- flags: Separator,
3044
- id: 'separator2',
3045
- label: ''
3046
- }, {
3047
- command: 'Extensions.installAnotherVersion',
3048
- flags: Disabled,
3049
- id: 'installAnotherVersion',
3050
- label: installAnotherVersion$1()
3051
- }, {
3052
- command: 'Extensions.copyExtensionInfo',
3053
- flags: None$1,
3054
- id: 'copy',
3055
- label: copy()
3056
- }, {
3057
- command: 'Extensions.copyExtensionId',
3058
- flags: None$1,
3059
- id: 'copyExtensionId',
3060
- label: copyExtensionId$1()
3061
- }];
2994
+
2995
+ const compareNodes = (oldNode, newNode) => {
2996
+ // Check if node type changed - return null to signal incompatible nodes
2997
+ // (caller should handle this with a Replace operation)
2998
+ if (oldNode.type !== newNode.type) {
2999
+ return null;
3000
+ }
3001
+ const patches = [];
3002
+ // Handle reference nodes - special handling for uid changes
3003
+ if (oldNode.type === Reference && oldNode.uid !== newNode.uid) {
3004
+ patches.push({
3005
+ type: SetReferenceNodeUid,
3006
+ uid: newNode.uid
3007
+ });
3008
+ }
3009
+ // Handle text nodes
3010
+ if (oldNode.type === Text && newNode.type === Text) {
3011
+ if (oldNode.text !== newNode.text) {
3012
+ patches.push({
3013
+ type: SetText,
3014
+ value: newNode.text
3015
+ });
3016
+ }
3017
+ return patches;
3018
+ }
3019
+ // Compare attributes
3020
+ const oldKeys = getKeys(oldNode).filter(key => oldNode.type !== Reference || key !== 'uid');
3021
+ const newKeys = getKeys(newNode).filter(key => newNode.type !== Reference || key !== 'uid');
3022
+ // Check for attribute changes
3023
+ for (const key of newKeys) {
3024
+ if (oldNode[key] !== newNode[key]) {
3025
+ patches.push({
3026
+ type: SetAttribute,
3027
+ key,
3028
+ value: newNode[key]
3029
+ });
3030
+ }
3031
+ }
3032
+ // Check for removed attributes
3033
+ for (const key of oldKeys) {
3034
+ if (!Object.hasOwn(newNode, key)) {
3035
+ patches.push({
3036
+ type: RemoveAttribute,
3037
+ key
3038
+ });
3039
+ }
3040
+ }
3041
+ return patches;
3062
3042
  };
3063
3043
 
3064
- const getMenuEntriesFilter = () => {
3065
- return [{
3066
- command: 'Extensions.filterByFeatured',
3067
- flags: None$1,
3068
- id: 'filterByFeatured',
3069
- label: featured()
3070
- }, {
3071
- command: 'Extensions.filterByMcpServers',
3072
- flags: None$1,
3073
- id: 'filterByMcpServers',
3074
- label: mcpServers()
3075
- }, {
3076
- command: 'Extensions.filterByMostPopular',
3077
- flags: None$1,
3078
- id: 'filterByMostPopular',
3079
- label: mostPopular()
3080
- }, {
3081
- command: 'Extensions.filterByRecentlyPublished',
3082
- flags: None$1,
3083
- id: 'filterByRecentlyPublished',
3084
- label: recentlyPublished()
3085
- }, {
3086
- command: 'Extensions.filterByRecommended',
3087
- flags: None$1,
3088
- id: 'filterByRecommended',
3089
- label: recommended()
3090
- }, {
3091
- command: '',
3092
- flags: Separator,
3093
- id: 'separator1',
3094
- label: ''
3095
- }, {
3096
- command: 'SearchExtensions.filterByCategory',
3097
- flags: SubMenu,
3098
- id: 'filterByCategory',
3099
- label: category()
3100
- }, {
3101
- command: 'Extensions.filterByInstalled',
3102
- flags: None$1,
3103
- id: 'filterByInstalled',
3104
- label: installed()
3105
- }, {
3106
- command: 'Extensions.filterByUpdates',
3107
- flags: None$1,
3108
- id: 'filterByUpdates',
3109
- label: updates()
3110
- }, {
3111
- command: 'Extensions.filterByBuiltin',
3112
- flags: None$1,
3113
- id: 'filterByBuiltin',
3114
- label: builtIn()
3115
- }, {
3116
- command: 'Extensions.filterByLinked',
3117
- flags: None$1,
3118
- id: 'filterByLinked',
3119
- label: linked()
3120
- }, {
3121
- command: 'Extensions.filterByEnabled',
3122
- flags: None$1,
3123
- id: 'filterByEnabled',
3124
- label: enabled()
3125
- }, {
3126
- command: 'Extensions.filterByDisabled',
3127
- flags: None$1,
3128
- id: 'filterByDisabled',
3129
- label: disabled()
3130
- }, {
3131
- command: 'Extensions.filterByWorkspaceUnsupported',
3132
- flags: None$1,
3133
- id: 'filterByWorkspaceUnsupported',
3134
- label: workspaceUnsupported()
3135
- }, {
3136
- command: '',
3137
- flags: Separator,
3138
- id: 'separator2',
3139
- label: ''
3140
- }, {
3141
- command: 'SearchExtensions.filterBySortBy',
3142
- flags: SubMenu,
3143
- id: 'filterBySortBy',
3144
- label: sortBy()
3145
- }];
3044
+ const treeToArray = node => {
3045
+ const result = [];
3046
+ const stack = [node];
3047
+ while (stack.length > 0) {
3048
+ const current = stack.pop();
3049
+ result.push(current.node);
3050
+ for (let i = current.children.length - 1; i >= 0; i--) {
3051
+ stack.push(current.children[i]);
3052
+ }
3053
+ }
3054
+ return result;
3146
3055
  };
3147
3056
 
3148
- const getMenuEntries2 = (state, props) => {
3149
- const {
3150
- menuId
3151
- } = props;
3152
- switch (menuId) {
3153
- case ExtensionSearchFilter:
3154
- return getMenuEntriesFilter();
3155
- default:
3156
- return getMenuEntriesList(props.builtin, props.disabled, props.status);
3057
+ const navigateToChild = (patches, currentChildIndex, index) => {
3058
+ if (currentChildIndex === -1) {
3059
+ patches.push({
3060
+ type: NavigateChild,
3061
+ index
3062
+ });
3063
+ return index;
3064
+ }
3065
+ if (currentChildIndex !== index) {
3066
+ patches.push({
3067
+ type: NavigateSibling,
3068
+ index
3069
+ });
3157
3070
  }
3071
+ return index;
3158
3072
  };
3159
-
3160
- const getMenuIds = () => {
3161
- return [ManageExtension, ExtensionSearchFilter];
3073
+ const navigateToParent = (patches, currentChildIndex) => {
3074
+ if (currentChildIndex >= 0) {
3075
+ patches.push({
3076
+ type: NavigateParent
3077
+ });
3078
+ }
3079
+ return -1;
3162
3080
  };
3163
-
3164
- const handleBlur = state => {
3165
- return {
3166
- ...state,
3167
- focus: None$2,
3168
- suggestOpen: false
3169
- };
3081
+ const addTree = (newNode, patches) => {
3082
+ patches.push({
3083
+ type: Add,
3084
+ nodes: treeToArray(newNode)
3085
+ });
3170
3086
  };
3171
-
3172
- const getExtensionDetailUri = extensionId => {
3173
- return `extension-detail://${extensionId}`;
3087
+ const replaceTree = (newNode, patches) => {
3088
+ patches.push({
3089
+ type: Replace,
3090
+ nodes: treeToArray(newNode)
3091
+ });
3174
3092
  };
3175
-
3176
- const openUri = async uri => {
3177
- return openUri$1(uri);
3093
+ const diffExistingChild = (oldNode, newNode, patches, currentChildIndex, index) => {
3094
+ const nodePatches = compareNodes(oldNode.node, newNode.node);
3095
+ if (nodePatches === null) {
3096
+ const nextChildIndex = navigateToChild(patches, currentChildIndex, index);
3097
+ replaceTree(newNode, patches);
3098
+ return nextChildIndex;
3099
+ }
3100
+ const hasChildrenToCompare = oldNode.children.length > 0 || newNode.children.length > 0;
3101
+ if (nodePatches.length === 0 && !hasChildrenToCompare) {
3102
+ return currentChildIndex;
3103
+ }
3104
+ const nextChildIndex = navigateToChild(patches, currentChildIndex, index);
3105
+ if (nodePatches.length > 0) {
3106
+ patches.push(...nodePatches);
3107
+ }
3108
+ if (hasChildrenToCompare) {
3109
+ diffChildren(oldNode.children, newNode.children, patches);
3110
+ }
3111
+ return nextChildIndex;
3178
3112
  };
3179
-
3180
- const selectIndex = (state, index) => {
3181
- return {
3182
- ...state,
3183
- focusedIndex: index
3184
- };
3113
+ const diffRootNode = (oldNode, newNode, patches) => {
3114
+ const nodePatches = compareNodes(oldNode.node, newNode.node);
3115
+ if (nodePatches === null) {
3116
+ replaceTree(newNode, patches);
3117
+ return;
3118
+ }
3119
+ if (nodePatches.length > 0) {
3120
+ patches.push(...nodePatches);
3121
+ }
3122
+ if (oldNode.children.length > 0 || newNode.children.length > 0) {
3123
+ diffChildren(oldNode.children, newNode.children, patches);
3124
+ }
3185
3125
  };
3186
-
3187
- const handleClick = async (state, index) => {
3188
- const {
3189
- items,
3190
- minLineY
3191
- } = state;
3192
- const actualIndex = index + minLineY;
3193
- if (actualIndex < 0 || actualIndex >= items.length) {
3194
- return {
3195
- ...state,
3196
- focus: List$2,
3197
- focusedIndex: -1
3198
- };
3126
+ const diffChildren = (oldChildren, newChildren, patches) => {
3127
+ const maxLength = Math.max(oldChildren.length, newChildren.length);
3128
+ let currentChildIndex = -1;
3129
+ const indicesToRemove = [];
3130
+ for (let i = 0; i < maxLength; i++) {
3131
+ const oldNode = oldChildren[i];
3132
+ const newNode = newChildren[i];
3133
+ if (!oldNode && !newNode) {
3134
+ continue;
3135
+ }
3136
+ if (!oldNode) {
3137
+ currentChildIndex = navigateToParent(patches, currentChildIndex);
3138
+ addTree(newNode, patches);
3139
+ continue;
3140
+ }
3141
+ if (!newNode) {
3142
+ indicesToRemove.push(i);
3143
+ continue;
3144
+ }
3145
+ currentChildIndex = diffExistingChild(oldNode, newNode, patches, currentChildIndex, i);
3146
+ }
3147
+ navigateToParent(patches, currentChildIndex);
3148
+ for (let j = indicesToRemove.length - 1; j >= 0; j--) {
3149
+ patches.push({
3150
+ type: RemoveChild,
3151
+ index: indicesToRemove[j]
3152
+ });
3199
3153
  }
3200
- const extension = items[actualIndex];
3201
- const uri = getExtensionDetailUri(extension.id);
3202
- await openUri(uri);
3203
- const partialNewState = selectIndex(state, actualIndex);
3204
- const newState = {
3205
- ...partialNewState,
3206
- focus: List$2
3207
- };
3208
- return newState;
3209
3154
  };
3210
-
3211
- const mergeClassNames = (...classNames) => {
3212
- return classNames.filter(Boolean).join(' ');
3155
+ const diffTrees = (oldTree, newTree, patches, path) => {
3156
+ if (path.length === 0 && oldTree.length === 1 && newTree.length === 1) {
3157
+ diffRootNode(oldTree[0], newTree[0], patches);
3158
+ return;
3159
+ }
3160
+ diffChildren(oldTree, newTree, patches);
3213
3161
  };
3214
3162
 
3215
- const text = data => {
3216
- return {
3217
- childCount: 0,
3218
- text: data,
3219
- type: Text
3220
- };
3163
+ const removeTrailingNavigationPatches = patches => {
3164
+ while (patches.length > 0) {
3165
+ const patch = patches.at(-1);
3166
+ if (patch.type !== NavigateChild && patch.type !== NavigateParent && patch.type !== NavigateSibling) {
3167
+ break;
3168
+ }
3169
+ patches.pop();
3170
+ }
3171
+ return patches;
3221
3172
  };
3222
3173
 
3223
- new Set(Object.values(VirtualDomElements));
3174
+ const diffTree = (oldNodes, newNodes) => {
3175
+ // Step 1: Convert flat arrays to tree structures
3176
+ const oldTree = arrayToTree(oldNodes);
3177
+ const newTree = arrayToTree(newNodes);
3178
+ // Step 3: Compare the trees
3179
+ const patches = [];
3180
+ diffTrees(oldTree, newTree, patches, []);
3181
+ // Remove trailing navigation patches since they serve no purpose
3182
+ return removeTrailingNavigationPatches(patches);
3183
+ };
3224
3184
 
3225
- const SetText = 1;
3226
- const Replace = 2;
3227
- const SetAttribute = 3;
3228
- const RemoveAttribute = 4;
3229
- const Add = 6;
3230
- const NavigateChild = 7;
3231
- const NavigateParent = 8;
3232
- const RemoveChild = 9;
3233
- const NavigateSibling = 10;
3234
- const SetReferenceNodeUid = 11;
3235
-
3236
- const isKey = key => {
3237
- return key !== 'type' && key !== 'childCount';
3238
- };
3185
+ const Actions = 'Actions';
3186
+ const ExtensionActions = 'ExtensionActions';
3187
+ const ExtensionActionButton = 'ExtensionActionButton';
3188
+ const ExtensionActive = 'ExtensionActive';
3189
+ const ExtensionHeader = 'ExtensionHeader';
3190
+ const ExtensionListItem = 'ExtensionListItem';
3191
+ const ExtensionListItemActionInstall = 'ExtensionListItemActionInstall';
3192
+ const ExtensionListItemAuthorName = 'ExtensionListItemAuthorName';
3193
+ const ExtensionListItemDescription = 'ExtensionListItemDescription';
3194
+ const ExtensionListItemDetail = 'ExtensionListItemDetail';
3195
+ const ExtensionListItemDisabled = 'ExtensionListItemDisabled';
3196
+ const ExtensionListItemDownloadCount = 'ExtensionListItemDownloadCount';
3197
+ const ExtensionListItemFooter = 'ExtensionListItemFooter';
3198
+ const ExtensionListItemIcon = 'ExtensionListItemIcon';
3199
+ const ExtensionListItemLinkedIcon = 'ExtensionListItemLinkedIcon';
3200
+ const ExtensionListItemMetadata = 'ExtensionListItemMetadata';
3201
+ const ExtensionListItemName = 'ExtensionListItemName';
3202
+ const ExtensionListItemRating = 'ExtensionListItemRating';
3203
+ const ExtensionListItemStatistic = 'ExtensionListItemStatistic';
3204
+ const ExtensionSearchCompletionHighlight = 'ExtensionSearchCompletionHighlight';
3205
+ const ExtensionSearchCompletionItem = 'ExtensionSearchCompletionItem';
3206
+ const ExtensionSearchCompletionItemFocused = 'ExtensionSearchCompletionItemFocused';
3207
+ const ExtensionSearchCompletionWidget = 'ExtensionSearchCompletionWidget';
3208
+ const Extensions$1 = 'Extensions';
3209
+ const FocusOutline = 'FocusOutline';
3210
+ const IconButton = 'IconButton';
3211
+ const List$1 = 'List';
3212
+ const ListItems = 'ListItems';
3213
+ const MaskIcon = 'MaskIcon';
3214
+ const MultilineInputBox = 'MultilineInputBox';
3215
+ const NoExtensionsFoundMessage = 'NoExtensionsFoundMessage';
3216
+ const ScrollBar = 'ScrollBar';
3217
+ const ScrollBarSmall = 'ScrollBarSmall';
3218
+ const ScrollBarThumb = 'ScrollBarThumb';
3219
+ const ScrollBarThumbActive = 'ScrollBarThumbActive';
3220
+ const SearchField = 'SearchField';
3221
+ const SearchFieldButton = 'SearchFieldButton';
3222
+ const SearchFieldButtonDisabled = 'SearchFieldButtonDisabled';
3223
+ const SearchFieldButtons = 'SearchFieldButtons';
3224
+ const SearchFieldContainer = 'SearchFieldContainer';
3225
+ const Viewlet = 'Viewlet';
3239
3226
 
3240
- const getKeys = node => {
3241
- const keys = Object.keys(node).filter(isKey);
3242
- return keys;
3243
- };
3227
+ const ComboBox = 'combobox';
3228
+ const Image = 'img';
3229
+ const List = 'list';
3230
+ const ListBox = 'listbox';
3231
+ const ListItem = 'listitem';
3232
+ const None$1 = 'none';
3233
+ const Option = 'option';
3234
+ const ToolBar = 'toolbar';
3244
3235
 
3245
- const arrayToTree = nodes => {
3246
- const result = [];
3247
- let i = 0;
3248
- while (i < nodes.length) {
3249
- const node = nodes[i];
3250
- const {
3251
- children,
3252
- nodesConsumed
3253
- } = getChildrenWithCount(nodes, i + 1, node.childCount || 0);
3254
- result.push({
3255
- node,
3256
- children
3257
- });
3258
- i += 1 + nodesConsumed;
3259
- }
3260
- return result;
3261
- };
3262
- const getChildrenWithCount = (nodes, startIndex, childCount) => {
3263
- if (childCount === 0) {
3264
- return {
3265
- children: [],
3266
- nodesConsumed: 0
3267
- };
3236
+ const getCompletionLabelVirtualDom = (label, highlights) => {
3237
+ const dom = [];
3238
+ let position = 0;
3239
+ for (let i = 0; i < highlights.length; i += 2) {
3240
+ const start = highlights[i];
3241
+ const end = highlights[i + 1];
3242
+ if (position < start) {
3243
+ dom.push(text(label.slice(position, start)));
3244
+ }
3245
+ dom.push({
3246
+ childCount: 1,
3247
+ className: ExtensionSearchCompletionHighlight,
3248
+ name: label,
3249
+ type: Span
3250
+ }, text(label.slice(start, end)));
3251
+ position = end;
3268
3252
  }
3269
- const children = [];
3270
- let i = startIndex;
3271
- let remaining = childCount;
3272
- let totalConsumed = 0;
3273
- while (remaining > 0 && i < nodes.length) {
3274
- const node = nodes[i];
3275
- const nodeChildCount = node.childCount || 0;
3276
- const {
3277
- children: nodeChildren,
3278
- nodesConsumed
3279
- } = getChildrenWithCount(nodes, i + 1, nodeChildCount);
3280
- children.push({
3281
- node,
3282
- children: nodeChildren
3283
- });
3284
- const nodeSize = 1 + nodesConsumed;
3285
- i += nodeSize;
3286
- totalConsumed += nodeSize;
3287
- remaining--;
3253
+ if (position < label.length) {
3254
+ dom.push(text(label.slice(position)));
3288
3255
  }
3289
- return {
3290
- children,
3291
- nodesConsumed: totalConsumed
3292
- };
3256
+ return dom;
3293
3257
  };
3294
3258
 
3295
- const compareNodes = (oldNode, newNode) => {
3296
- // Check if node type changed - return null to signal incompatible nodes
3297
- // (caller should handle this with a Replace operation)
3298
- if (oldNode.type !== newNode.type) {
3299
- return null;
3300
- }
3301
- const patches = [];
3302
- // Handle reference nodes - special handling for uid changes
3303
- if (oldNode.type === Reference && oldNode.uid !== newNode.uid) {
3304
- patches.push({
3305
- type: SetReferenceNodeUid,
3306
- uid: newNode.uid
3307
- });
3308
- }
3309
- // Handle text nodes
3310
- if (oldNode.type === Text && newNode.type === Text) {
3311
- if (oldNode.text !== newNode.text) {
3312
- patches.push({
3313
- type: SetText,
3314
- value: newNode.text
3315
- });
3316
- }
3317
- return patches;
3318
- }
3319
- // Compare attributes
3320
- const oldKeys = getKeys(oldNode).filter(key => oldNode.type !== Reference || key !== 'uid');
3321
- const newKeys = getKeys(newNode).filter(key => newNode.type !== Reference || key !== 'uid');
3322
- // Check for attribute changes
3323
- for (const key of newKeys) {
3324
- if (oldNode[key] !== newNode[key]) {
3325
- patches.push({
3326
- type: SetAttribute,
3327
- key,
3328
- value: newNode[key]
3329
- });
3259
+ const getRootNodeCount = nodes => {
3260
+ let count = 0;
3261
+ const remainingChildCounts = [];
3262
+ for (const node of nodes) {
3263
+ while (remainingChildCounts.length > 0 && remainingChildCounts.at(-1) === 0) {
3264
+ remainingChildCounts.pop();
3330
3265
  }
3331
- }
3332
- // Check for removed attributes
3333
- for (const key of oldKeys) {
3334
- if (!Object.hasOwn(newNode, key)) {
3335
- patches.push({
3336
- type: RemoveAttribute,
3337
- key
3338
- });
3266
+ if (remainingChildCounts.length === 0) {
3267
+ count++;
3268
+ } else {
3269
+ const lastIndex = remainingChildCounts.length - 1;
3270
+ remainingChildCounts[lastIndex]--;
3339
3271
  }
3272
+ remainingChildCounts.push(node.childCount);
3340
3273
  }
3341
- return patches;
3274
+ return count;
3342
3275
  };
3343
-
3344
- const treeToArray = node => {
3345
- const result = [];
3346
- const stack = [node];
3347
- while (stack.length > 0) {
3348
- const current = stack.pop();
3349
- result.push(current.node);
3350
- for (let i = current.children.length - 1; i >= 0; i--) {
3351
- stack.push(current.children[i]);
3352
- }
3353
- }
3354
- return result;
3276
+ const getCompletionItemVirtualDom = (item, index, focusedIndex) => {
3277
+ const labelDom = getCompletionLabelVirtualDom(item.label, item.highlights);
3278
+ const focused = index === focusedIndex;
3279
+ return [{
3280
+ ariaSelected: focused,
3281
+ childCount: getRootNodeCount(labelDom),
3282
+ className: focused ? mergeClassNames(ExtensionSearchCompletionItem, ExtensionSearchCompletionItemFocused) : ExtensionSearchCompletionItem,
3283
+ id: `ExtensionSearchCompletion-${index}`,
3284
+ name: item.label,
3285
+ onPointerDown: HandlePointerDown,
3286
+ role: Option,
3287
+ type: Button$2
3288
+ }, ...labelDom];
3355
3289
  };
3356
3290
 
3357
- const navigateToChild = (patches, currentChildIndex, index) => {
3358
- if (currentChildIndex === -1) {
3359
- patches.push({
3360
- type: NavigateChild,
3361
- index
3362
- });
3363
- return index;
3364
- }
3365
- if (currentChildIndex !== index) {
3366
- patches.push({
3367
- type: NavigateSibling,
3368
- index
3369
- });
3370
- }
3371
- return index;
3291
+ const getCompletionWidgetVirtualDom = (items, focusedIndex) => {
3292
+ return [{
3293
+ ariaLabel: 'Extension search completions',
3294
+ childCount: items.length,
3295
+ className: ExtensionSearchCompletionWidget,
3296
+ id: 'ExtensionSearchCompletions',
3297
+ role: ListBox,
3298
+ type: Div
3299
+ }, ...items.flatMap((item, index) => getCompletionItemVirtualDom(item, index, focusedIndex))];
3372
3300
  };
3373
- const navigateToParent = (patches, currentChildIndex) => {
3374
- if (currentChildIndex >= 0) {
3375
- patches.push({
3376
- type: NavigateParent
3377
- });
3301
+
3302
+ const Focusable = 0;
3303
+
3304
+ const disabledClassName = mergeClassNames(SearchFieldButton, SearchFieldButtonDisabled);
3305
+ const getClassName = enabled => {
3306
+ if (enabled) {
3307
+ return SearchFieldButton;
3378
3308
  }
3379
- return -1;
3380
- };
3381
- const addTree = (newNode, patches) => {
3382
- patches.push({
3383
- type: Add,
3384
- nodes: treeToArray(newNode)
3385
- });
3309
+ return disabledClassName;
3386
3310
  };
3387
- const replaceTree = (newNode, patches) => {
3388
- patches.push({
3389
- type: Replace,
3390
- nodes: treeToArray(newNode)
3391
- });
3392
- };
3393
- const diffExistingChild = (oldNode, newNode, patches, currentChildIndex, index) => {
3394
- const nodePatches = compareNodes(oldNode.node, newNode.node);
3395
- if (nodePatches === null) {
3396
- const nextChildIndex = navigateToChild(patches, currentChildIndex, index);
3397
- replaceTree(newNode, patches);
3398
- return nextChildIndex;
3399
- }
3400
- const hasChildrenToCompare = oldNode.children.length > 0 || newNode.children.length > 0;
3401
- if (nodePatches.length === 0 && !hasChildrenToCompare) {
3402
- return currentChildIndex;
3403
- }
3404
- const nextChildIndex = navigateToChild(patches, currentChildIndex, index);
3405
- if (nodePatches.length > 0) {
3406
- patches.push(...nodePatches);
3407
- }
3408
- if (hasChildrenToCompare) {
3409
- diffChildren(oldNode.children, newNode.children, patches);
3410
- }
3411
- return nextChildIndex;
3311
+ const getSearchFieldButtonVirtualDom = button => {
3312
+ const {
3313
+ enabled,
3314
+ icon,
3315
+ onClick,
3316
+ title
3317
+ } = button;
3318
+ return [{
3319
+ childCount: 1,
3320
+ className: getClassName(enabled),
3321
+ onClick,
3322
+ tabIndex: Focusable,
3323
+ title,
3324
+ type: Button$2
3325
+ }, {
3326
+ childCount: 0,
3327
+ className: mergeClassNames(MaskIcon, icon),
3328
+ type: Div
3329
+ }];
3412
3330
  };
3413
- const diffRootNode = (oldNode, newNode, patches) => {
3414
- const nodePatches = compareNodes(oldNode.node, newNode.node);
3415
- if (nodePatches === null) {
3416
- replaceTree(newNode, patches);
3417
- return;
3418
- }
3419
- if (nodePatches.length > 0) {
3420
- patches.push(...nodePatches);
3421
- }
3422
- if (oldNode.children.length > 0 || newNode.children.length > 0) {
3423
- diffChildren(oldNode.children, newNode.children, patches);
3424
- }
3331
+
3332
+ const searchFieldNode = {
3333
+ childCount: 2,
3334
+ className: SearchField,
3335
+ role: None$1,
3336
+ type: Div
3425
3337
  };
3426
- const diffChildren = (oldChildren, newChildren, patches) => {
3427
- const maxLength = Math.max(oldChildren.length, newChildren.length);
3428
- let currentChildIndex = -1;
3429
- const indicesToRemove = [];
3430
- for (let i = 0; i < maxLength; i++) {
3431
- const oldNode = oldChildren[i];
3432
- const newNode = newChildren[i];
3433
- if (!oldNode && !newNode) {
3434
- continue;
3435
- }
3436
- if (!oldNode) {
3437
- currentChildIndex = navigateToParent(patches, currentChildIndex);
3438
- addTree(newNode, patches);
3439
- continue;
3440
- }
3441
- if (!newNode) {
3442
- indicesToRemove.push(i);
3443
- continue;
3444
- }
3445
- currentChildIndex = diffExistingChild(oldNode, newNode, patches, currentChildIndex, i);
3446
- }
3447
- navigateToParent(patches, currentChildIndex);
3448
- for (let j = indicesToRemove.length - 1; j >= 0; j--) {
3449
- patches.push({
3450
- type: RemoveChild,
3451
- index: indicesToRemove[j]
3338
+ const getSearchFieldVirtualDom = (name, placeholder, onInput, insideButtons, outsideButtons, onFocus = '', onBlur = '', inputProperties = {}) => {
3339
+ // TODO avoid mutation
3340
+ const dom = [searchFieldNode, {
3341
+ autocapitalize: 'off',
3342
+ autocomplete: 'off',
3343
+ autocorrect: 'off',
3344
+ childCount: 0,
3345
+ className: MultilineInputBox,
3346
+ inputType: 'search',
3347
+ name,
3348
+ ...(onBlur && {
3349
+ onBlur
3350
+ }),
3351
+ onFocus,
3352
+ onInput,
3353
+ placeholder,
3354
+ spellcheck: false,
3355
+ type: Input$1,
3356
+ ...inputProperties
3357
+ }, {
3358
+ childCount: insideButtons.length,
3359
+ className: SearchFieldButtons,
3360
+ type: Div
3361
+ }, ...insideButtons.flatMap(getSearchFieldButtonVirtualDom)];
3362
+ if (outsideButtons.length > 0) {
3363
+ dom.unshift({
3364
+ childCount: 1 + outsideButtons.length,
3365
+ className: SearchFieldContainer,
3366
+ role: None$1,
3367
+ type: Div
3452
3368
  });
3369
+ dom.push(...outsideButtons.flatMap(getSearchFieldButtonVirtualDom));
3453
3370
  }
3454
- };
3455
- const diffTrees = (oldTree, newTree, patches, path) => {
3456
- if (path.length === 0 && oldTree.length === 1 && newTree.length === 1) {
3457
- diffRootNode(oldTree[0], newTree[0], patches);
3458
- return;
3459
- }
3460
- diffChildren(oldTree, newTree, patches);
3371
+ return dom;
3461
3372
  };
3462
3373
 
3463
- const removeTrailingNavigationPatches = patches => {
3464
- while (patches.length > 0) {
3465
- const patch = patches.at(-1);
3466
- if (patch.type !== NavigateChild && patch.type !== NavigateParent && patch.type !== NavigateSibling) {
3467
- break;
3468
- }
3469
- patches.pop();
3374
+ const Extensions = 'extensions';
3375
+
3376
+ const getCompletionVirtualDom = (completionItems, completionFocusedIndex, suggestOpen) => {
3377
+ if (!suggestOpen) {
3378
+ return [];
3470
3379
  }
3471
- return patches;
3380
+ return getCompletionWidgetVirtualDom(completionItems, completionFocusedIndex);
3472
3381
  };
3473
-
3474
- const diffTree = (oldNodes, newNodes) => {
3475
- // Step 1: Convert flat arrays to tree structures
3476
- const oldTree = arrayToTree(oldNodes);
3477
- const newTree = arrayToTree(newNodes);
3478
- // Step 3: Compare the trees
3479
- const patches = [];
3480
- diffTrees(oldTree, newTree, patches, []);
3481
- // Remove trailing navigation patches since they serve no purpose
3482
- return removeTrailingNavigationPatches(patches);
3382
+ const getExtensionHeaderVirtualDom = (placeholder, actions, completionItems = [], completionFocusedIndex = 0, suggestOpen = false) => {
3383
+ const inputProperties = suggestOpen ? {
3384
+ ariaActivedescendant: `ExtensionSearchCompletion-${completionFocusedIndex}`,
3385
+ ariaAutoComplete: 'list',
3386
+ ariaControls: 'ExtensionSearchCompletions',
3387
+ ariaExpanded: true,
3388
+ role: ComboBox
3389
+ } : {
3390
+ ariaAutoComplete: 'list',
3391
+ ariaExpanded: false,
3392
+ role: ComboBox
3393
+ };
3394
+ const completionDom = getCompletionVirtualDom(completionItems, completionFocusedIndex, suggestOpen);
3395
+ return [{
3396
+ childCount: suggestOpen ? 2 : 1,
3397
+ className: ExtensionHeader,
3398
+ onContextMenu: HandleHeaderContextMenu,
3399
+ type: Div
3400
+ }, ...getSearchFieldVirtualDom(Extensions, placeholder, HandleExtensionsInput, actions, [], HandleInputFocus, HandleInputBlur, inputProperties), ...completionDom];
3483
3401
  };
3484
3402
 
3485
- const getListIndex = (eventX, eventY, x, y, deltaY, itemHeight, headerHeight) => {
3486
- const relativeDeltaY = deltaY % itemHeight;
3487
- const relativeY = eventY - y - headerHeight + relativeDeltaY;
3488
- const index = Math.floor(relativeY / itemHeight);
3489
- return index;
3490
- };
3403
+ const Extension = 'Extension';
3491
3404
 
3492
- const handleCompletionPointerDown = (state, label) => {
3493
- if (state.completionItems.every(item => item.label !== label)) {
3494
- return Promise.resolve(state);
3495
- }
3496
- return acceptCompletion(state, label);
3405
+ const install = {
3406
+ disabled: false,
3407
+ label: install$1(),
3408
+ onClick: HandleInstall
3497
3409
  };
3498
-
3499
- const handleClickAt = async (state, button, eventX, eventY, name = '') => {
3500
- if (name.startsWith('@')) {
3501
- return handleCompletionPointerDown(state, name);
3410
+ const installing = {
3411
+ disabled: true,
3412
+ label: installing$1(),
3413
+ onClick: HandleInstall
3414
+ };
3415
+ const enable = {
3416
+ disabled: false,
3417
+ label: enable$2(),
3418
+ onClick: HandleEnable
3419
+ };
3420
+ const disable = {
3421
+ disabled: false,
3422
+ label: disable$2(),
3423
+ onClick: HandleDisable
3424
+ };
3425
+ const uninstall = {
3426
+ disabled: false,
3427
+ label: uninstall$1(),
3428
+ onClick: HandleUninstall
3429
+ };
3430
+ const uninstalling = {
3431
+ disabled: true,
3432
+ label: uninstalling$1(),
3433
+ onClick: HandleUninstall
3434
+ };
3435
+ const getExtensionActions = (builtin, disabled, status) => {
3436
+ if (status === NotInstalled) {
3437
+ return [install];
3502
3438
  }
3503
- if (name) {
3504
- return state;
3439
+ if (status === Installing) {
3440
+ return [installing];
3505
3441
  }
3506
- if (button !== LeftClick) {
3507
- return state;
3442
+ if (status === Uninstalling) {
3443
+ return builtin ? [] : [uninstalling];
3508
3444
  }
3509
- const {
3510
- deltaY,
3511
- headerHeight,
3512
- itemHeight,
3513
- x,
3514
- y
3515
- } = state;
3516
- const index = getListIndex(eventX, eventY, x, y, deltaY, itemHeight, headerHeight);
3517
- return handleClick(state, index);
3518
- };
3519
-
3520
- const handleClickCurrent = state => {
3521
- const {
3522
- focusedIndex
3523
- } = state;
3524
- return handleClick(state, focusedIndex);
3525
- };
3526
-
3527
- const handleClickCurrentButKeepFocus = state => {
3528
- const {
3529
- focusedIndex
3530
- } = state;
3531
- return handleClick(state, focusedIndex);
3445
+ const enableOrDisable = isExtensionDisabled(disabled, status) ? enable : disable;
3446
+ return builtin ? [enableOrDisable] : [enableOrDisable, uninstall];
3532
3447
  };
3533
3448
 
3534
- const show2 = async (uid, menuId, x, y, args) => {
3535
- await showContextMenu2(uid, menuId, x, y, args);
3449
+ const className = mergeClassNames(ExtensionListItemActionInstall, ExtensionActionButton);
3450
+ const getExtensionActionVirtualDom = (action, id) => {
3451
+ return [{
3452
+ childCount: 1,
3453
+ className,
3454
+ disabled: action.disabled,
3455
+ name: id,
3456
+ onClick: action.onClick,
3457
+ type: Button$2
3458
+ }, text(action.label)];
3536
3459
  };
3537
3460
 
3538
- const handleClickFilter = async state => {
3539
- const {
3540
- headerHeight,
3541
- uid,
3542
- width,
3543
- x,
3544
- y
3545
- } = state;
3546
- const menuX = x + width + 60;
3547
- const menuHeight = 370;
3548
- const menuY = y + headerHeight + menuHeight;
3549
- await show2(uid, ExtensionSearchFilter, menuX, menuY, {
3550
- menuId: ExtensionSearchFilter
3551
- });
3552
- return state;
3461
+ const getExtensionActionsVirtualDom = (id, builtin, disabled, status) => {
3462
+ const actions = getExtensionActions(builtin, disabled, status);
3463
+ return [{
3464
+ childCount: actions.length,
3465
+ className: ExtensionActions,
3466
+ type: Div
3467
+ }, ...actions.flatMap(action => getExtensionActionVirtualDom(action, id))];
3553
3468
  };
3554
3469
 
3555
- const handleContextMenu = async (state, button, eventX, eventY) => {
3556
- // TODO use focused index when when context menu button is -1 (keyboard)
3557
- const {
3558
- deltaY,
3559
- headerHeight,
3560
- itemHeight,
3561
- items,
3562
- minLineY,
3563
- uid,
3564
- x,
3565
- y
3566
- } = state;
3567
- const visibleIndex = getListIndex(eventX, eventY, x, y, deltaY, itemHeight, headerHeight);
3568
- const index = visibleIndex + minLineY;
3569
- if (index < 0 || index >= items.length) {
3570
- return state;
3571
- }
3572
- const item = items[index];
3573
- await show2(uid, ManageExtension, eventX, eventY, {
3574
- builtin: item.builtin === true,
3575
- disabled: item.disabled === true,
3576
- menuId: ManageExtension,
3577
- status: item.status
3578
- });
3470
+ const getExtensionListItemClassName = (focused, disabled) => {
3471
+ return mergeClassNames(ExtensionListItem, focused ? ExtensionActive : '', disabled ? ExtensionListItemDisabled : '');
3472
+ };
3473
+
3474
+ const getExtensionListItemFooter = hasStatistics => {
3579
3475
  return {
3580
- ...state,
3581
- focusedIndex: index
3476
+ childCount: hasStatistics ? 3 : 2,
3477
+ className: ExtensionListItemFooter,
3478
+ type: Div
3582
3479
  };
3583
3480
  };
3584
3481
 
3585
- const handleDisableWorkspace = (state, id) => {
3586
- return state;
3482
+ const getExtensionListItemId = focused => {
3483
+ if (focused) {
3484
+ return `ExtensionActive`;
3485
+ }
3486
+ return undefined;
3587
3487
  };
3588
3488
 
3589
- const handleEnableWorkspace = (state, id) => {
3590
- return state;
3489
+ const extensionListItemMetadataNode = {
3490
+ childCount: 2,
3491
+ className: ExtensionListItemMetadata,
3492
+ type: Div
3493
+ };
3494
+ const getStatisticVirtualDom = (label, value, className) => {
3495
+ const accessibleLabel = `${label}: ${value}`;
3496
+ return [{
3497
+ ariaLabel: accessibleLabel,
3498
+ childCount: 1,
3499
+ className: mergeClassNames(ExtensionListItemStatistic, className),
3500
+ title: accessibleLabel,
3501
+ type: Span
3502
+ }, text(value)];
3503
+ };
3504
+ const getExtensionStatisticsVirtualDom = (downloadCount, rating$1) => {
3505
+ return [extensionListItemMetadataNode, ...getStatisticVirtualDom(downloads(), downloadCount, ExtensionListItemDownloadCount), ...getStatisticVirtualDom(rating(), rating$1, ExtensionListItemRating)];
3591
3506
  };
3592
3507
 
3593
- const Web = 1;
3594
- const Electron = 2;
3595
- const Remote = 3;
3596
-
3597
- const getAllExtensions$1 = async (assetDir, platform) => {
3598
- try {
3599
- return await invoke$3('Extensions.getAllExtensions', assetDir, platform);
3600
- } catch (error) {
3601
- if (platform === Web) {
3602
- return [];
3603
- }
3604
- throw error;
3508
+ const getExtensionListItemStatisticsVirtualDom = (hasStatistics, downloadCount, rating) => {
3509
+ if (!hasStatistics) {
3510
+ return [];
3605
3511
  }
3512
+ return getExtensionStatisticsVirtualDom(downloadCount, rating);
3606
3513
  };
3607
3514
 
3608
- const getAllExtensions = (assetDir, platform) => {
3609
- return getAllExtensions$1(assetDir, platform);
3515
+ const getListItemDetail = linked => {
3516
+ return {
3517
+ childCount: linked ? 4 : 3,
3518
+ className: ExtensionListItemDetail,
3519
+ type: Div
3520
+ };
3610
3521
  };
3611
-
3612
- const getBuiltin = extension => {
3613
- if (extension === null || typeof extension !== 'object') {
3614
- return false;
3522
+ const listItemName = {
3523
+ childCount: 1,
3524
+ className: ExtensionListItemName,
3525
+ type: Div
3526
+ };
3527
+ const listItemDescription = {
3528
+ childCount: 1,
3529
+ className: ExtensionListItemDescription,
3530
+ type: Div
3531
+ };
3532
+ const listItemAuthorName = {
3533
+ childCount: 1,
3534
+ className: ExtensionListItemAuthorName,
3535
+ type: Div
3536
+ };
3537
+ const getLinkedIconVirtualDom = linked$1 => {
3538
+ if (!linked$1) {
3539
+ return [];
3615
3540
  }
3541
+ const label = linked();
3542
+ return [{
3543
+ ariaLabel: label,
3544
+ childCount: 0,
3545
+ className: mergeClassNames(MaskIcon, 'MaskIconLinkExternal', ExtensionListItemLinkedIcon),
3546
+ role: Image,
3547
+ title: 'Extension is linked',
3548
+ type: Div
3549
+ }];
3550
+ };
3551
+ const getExtensionListItemVirtualDom = extension => {
3616
3552
  const {
3617
- builtin,
3553
+ builtin = false,
3554
+ description,
3555
+ disabled = false,
3556
+ downloadCount = 'n/a',
3557
+ focused,
3558
+ icon,
3618
3559
  id,
3619
- isBuiltin
3560
+ linked = false,
3561
+ name,
3562
+ posInSet,
3563
+ publisher,
3564
+ rating = 'n/a',
3565
+ setSize,
3566
+ status
3620
3567
  } = extension;
3621
- return isBuiltin === true || builtin === true || typeof id === 'string' && id.startsWith('builtin.');
3568
+ const actionsDom = getExtensionActionsVirtualDom(id, builtin, disabled, status);
3569
+ const hasStatistics = !builtin;
3570
+ const dom = [{
3571
+ ariaPosInSet: posInSet,
3572
+ ariaRoleDescription: Extension,
3573
+ ariaSetSize: setSize,
3574
+ childCount: 2,
3575
+ className: getExtensionListItemClassName(focused, disabled),
3576
+ id: getExtensionListItemId(focused),
3577
+ role: ListItem,
3578
+ type: Div
3579
+ }, {
3580
+ childCount: 0,
3581
+ className: ExtensionListItemIcon,
3582
+ role: None$1,
3583
+ src: icon,
3584
+ type: Img
3585
+ }, getListItemDetail(linked), listItemName, text(name), listItemDescription, text(description), getExtensionListItemFooter(hasStatistics), listItemAuthorName, text(publisher), ...getExtensionListItemStatisticsVirtualDom(hasStatistics, downloadCount, rating), ...actionsDom, ...getLinkedIconVirtualDom(linked)];
3586
+ return dom;
3622
3587
  };
3623
3588
 
3624
- const isString = item => {
3625
- return typeof item === 'string';
3589
+ const getListClassName = focusOutline => {
3590
+ const className = focusOutline ? mergeClassNames(ListItems, FocusOutline) : ListItems;
3591
+ return className;
3626
3592
  };
3627
-
3628
- const getCategories = extension => {
3629
- if (extension === null || typeof extension !== 'object' || !('categories' in extension) || !Array.isArray(extension.categories)) {
3630
- return [];
3631
- }
3632
- return extension.categories.filter(isString);
3593
+ const getExtensionsListVirtualDom = (visibleExtensions, focusOutline) => {
3594
+ const dom = [{
3595
+ ariaLabel: extensions(),
3596
+ childCount: visibleExtensions.length,
3597
+ className: getListClassName(focusOutline),
3598
+ onBlur: HandleBlur,
3599
+ onContextmenu: HandleContextMenu,
3600
+ onContextMenu: HandleContextMenu,
3601
+ onFocus: HandleFocus,
3602
+ onPointerDown: HandlePointerDown,
3603
+ onTouchEnd: HandleTouchEnd,
3604
+ onTouchMove: HandleTouchMove,
3605
+ onTouchStart: HandleTouchStart,
3606
+ onWheel: HandleWheel,
3607
+ role: List,
3608
+ tabIndex: Focusable,
3609
+ type: Div
3610
+ }, ...visibleExtensions.flatMap(getExtensionListItemVirtualDom)];
3611
+ return dom;
3633
3612
  };
3634
3613
 
3635
- const getDescription = extension => {
3636
- if (extension === null || typeof extension !== 'object' || !('description' in extension) || typeof extension.description !== 'string' || !extension.description) {
3637
- return 'n/a';
3638
- }
3639
- return extension.description;
3614
+ const getExtensionsVirtualDom = (visibleExtensions, focusOutline) => {
3615
+ const dom = getExtensionsListVirtualDom(visibleExtensions, focusOutline);
3616
+ // TODO
3617
+ return dom;
3640
3618
  };
3641
3619
 
3642
- const getDisabled = extension => {
3643
- return extension !== null && typeof extension === 'object' && 'disabled' in extension && extension.disabled === true;
3620
+ const noExtensionsFoundNode = {
3621
+ childCount: 1,
3622
+ className: NoExtensionsFoundMessage,
3623
+ type: Div
3624
+ };
3625
+ const getNoExtensionsFoundVirtualDom = message => {
3626
+ return [noExtensionsFoundNode, text(message)];
3644
3627
  };
3645
3628
 
3646
- const getDownloadCountValue = extension => {
3647
- if (extension === null || typeof extension !== 'object') {
3648
- return undefined;
3649
- }
3650
- const marketplace = 'marketplace' in extension && extension.marketplace !== null && typeof extension.marketplace === 'object' ? extension.marketplace : {};
3651
- const packageJson = 'packageJSON' in extension && extension.packageJSON !== null && typeof extension.packageJSON === 'object' ? extension.packageJSON : {};
3652
- return ('downloadCount' in extension ? extension.downloadCount : undefined) ?? ('downloads' in extension ? extension.downloads : undefined) ?? ('downloadCount' in marketplace ? marketplace.downloadCount : undefined) ?? ('downloads' in marketplace ? marketplace.downloads : undefined) ?? ('downloadCount' in packageJson ? packageJson.downloadCount : undefined) ?? ('downloads' in packageJson ? packageJson.downloads : undefined);
3629
+ const scrollBarNode = {
3630
+ childCount: 1,
3631
+ className: mergeClassNames(ScrollBar, ScrollBarSmall),
3632
+ onPointerDown: HandleScrollBarPointerDown,
3633
+ // TODO support pointercapture event
3634
+ type: Div
3653
3635
  };
3654
- const getDownloadCount = extension => {
3655
- const downloadCount = getDownloadCountValue(extension);
3656
- if (typeof downloadCount !== 'number') {
3657
- return 'n/a';
3636
+ const scrollBarThumbNode = {
3637
+ childCount: 0,
3638
+ className: ScrollBarThumb,
3639
+ type: Div
3640
+ };
3641
+ const getScrollBarVirtualDom = (scrollBarHeight, scrollBarTop) => {
3642
+ const shouldShowScrollbar = scrollBarHeight > 0;
3643
+ if (!shouldShowScrollbar) {
3644
+ return [];
3658
3645
  }
3659
- return downloadCount.toLocaleString();
3646
+ return [scrollBarNode, scrollBarThumbNode];
3660
3647
  };
3661
3648
 
3662
- const getRemoteUrl = (extension, platform, assetDir) => {
3663
- if (extension === null || typeof extension !== 'object') {
3664
- return '';
3665
- }
3666
- const builtin = 'builtin' in extension && extension.builtin === true;
3667
- const icon = 'icon' in extension && typeof extension.icon === 'string' ? extension.icon : '';
3668
- const id = 'id' in extension && typeof extension.id === 'string' ? extension.id : '';
3669
- const path = 'path' in extension && typeof extension.path === 'string' ? extension.path : '';
3670
- if (platform === Remote || platform === Electron) {
3671
- if (builtin) {
3672
- return `${assetDir}/extensions/${id}/${icon}`;
3673
- }
3674
- return `/remote/${path}/${icon}`; // TODO support windows paths
3675
- }
3676
- if (platform === Web) {
3677
- return `${path}/${icon}`;
3678
- }
3679
- return '';
3680
- };
3681
-
3682
- const getExtensionDefaultIcon = assetDir => {
3683
- return `${assetDir}/icons/extensionDefaultIcon.png`;
3684
- };
3685
- const getExtensionLanguageBasicsIcon = assetDir => {
3686
- return `${assetDir}/icons/language-icon.svg`;
3687
- };
3688
- const getExtensionThemeIcon = assetDir => {
3689
- return `${assetDir}/icons/theme-icon.png`;
3690
- };
3691
-
3692
- const isLanguageBasicsExtension = extension => {
3693
- return 'name' in extension && typeof extension.name === 'string' && extension.name.startsWith('Language Basics');
3694
- };
3695
- const isThemeExtension = extension => {
3696
- return 'name' in extension && typeof extension.name === 'string' && extension.name.endsWith(' Theme');
3697
- };
3698
- const getExtensionIcon = (extension, platform, assetDir) => {
3699
- if (extension === null || typeof extension !== 'object') {
3700
- return getExtensionDefaultIcon(assetDir);
3701
- }
3702
- const hasIcon = 'icon' in extension && typeof extension.icon === 'string' && extension.icon;
3703
- const hasPath = 'path' in extension && typeof extension.path === 'string' && extension.path;
3704
- if (!hasPath || !hasIcon) {
3705
- if (isLanguageBasicsExtension(extension)) {
3706
- return getExtensionLanguageBasicsIcon(assetDir);
3707
- }
3708
- if (isThemeExtension(extension)) {
3709
- return getExtensionThemeIcon(assetDir);
3710
- }
3711
- return getExtensionDefaultIcon(assetDir);
3712
- }
3713
- return getRemoteUrl(extension, platform, assetDir);
3714
- };
3715
-
3716
- const getIcon = (extension, platform, assetDir) => {
3717
- return getExtensionIcon(extension, platform, assetDir);
3718
- };
3719
-
3720
- const getId = extension => {
3721
- if (extension === null || typeof extension !== 'object' || !('id' in extension) || typeof extension.id !== 'string' || !extension.id) {
3722
- return 'n/a';
3723
- }
3724
- return extension.id;
3725
- };
3726
-
3727
- const getLinked = extension => {
3728
- if (extension === null || typeof extension !== 'object') {
3729
- return false;
3730
- }
3731
- if ('linked' in extension && extension.linked === true) {
3732
- return true;
3733
- }
3734
- return 'symlink' in extension && typeof extension.symlink === 'string' && extension.symlink.length > 0;
3735
- };
3736
-
3737
- const getName = extension => {
3738
- if (extension === null || typeof extension !== 'object') {
3739
- return 'n/a';
3740
- }
3741
- if ('name' in extension && typeof extension.name === 'string' && extension.name) {
3742
- return extension.name;
3743
- }
3744
- if ('id' in extension && typeof extension.id === 'string' && extension.id) {
3745
- return extension.id;
3746
- }
3747
- return 'n/a';
3649
+ const getVisibleItem = (item, setSize, itemHeight, minLineY, relative, i, focusedIndex) => {
3650
+ // TODO use normal parameters
3651
+ const {
3652
+ builtin,
3653
+ description,
3654
+ disabled,
3655
+ downloadCount,
3656
+ icon,
3657
+ id,
3658
+ linked,
3659
+ name,
3660
+ publisher,
3661
+ rating,
3662
+ status
3663
+ } = item;
3664
+ return {
3665
+ builtin,
3666
+ description,
3667
+ disabled,
3668
+ downloadCount,
3669
+ focused: i === focusedIndex,
3670
+ icon,
3671
+ id,
3672
+ index: i,
3673
+ linked,
3674
+ name,
3675
+ posInSet: i + 1,
3676
+ publisher,
3677
+ rating,
3678
+ setSize,
3679
+ status,
3680
+ top: (i - minLineY) * itemHeight - relative
3681
+ };
3748
3682
  };
3749
3683
 
3750
- const RE_PUBLISHER = /^[a-z\d-]+/;
3751
- const getPublisher = extension => {
3752
- if (extension === null || typeof extension !== 'object' || !('id' in extension) || typeof extension.id !== 'string') {
3753
- return 'n/a';
3754
- }
3755
- const match = extension.id.match(RE_PUBLISHER);
3756
- if (!match) {
3757
- return 'n/a';
3684
+ const getVisible = state => {
3685
+ const {
3686
+ deltaY,
3687
+ focusedIndex,
3688
+ itemHeight,
3689
+ items,
3690
+ maxLineY,
3691
+ minLineY
3692
+ } = state;
3693
+ const setSize = items.length;
3694
+ const visible = [];
3695
+ const relative = deltaY % itemHeight;
3696
+ for (let i = minLineY; i < maxLineY; i++) {
3697
+ const item = items[i];
3698
+ visible.push(getVisibleItem(item, setSize, itemHeight, minLineY, relative, i, focusedIndex));
3758
3699
  }
3759
- return match[0];
3700
+ return visible;
3760
3701
  };
3761
3702
 
3762
- const getRatingValue = extension => {
3763
- if (extension === null || typeof extension !== 'object') {
3764
- return undefined;
3765
- }
3766
- const marketplace = 'marketplace' in extension && extension.marketplace !== null && typeof extension.marketplace === 'object' ? extension.marketplace : {};
3767
- const packageJson = 'packageJSON' in extension && extension.packageJSON !== null && typeof extension.packageJSON === 'object' ? extension.packageJSON : {};
3768
- return ('rating' in extension ? extension.rating : undefined) ?? ('averageRating' in extension ? extension.averageRating : undefined) ?? ('rating' in marketplace ? marketplace.rating : undefined) ?? ('averageRating' in marketplace ? marketplace.averageRating : undefined) ?? ('rating' in packageJson ? packageJson.rating : undefined) ?? ('averageRating' in packageJson ? packageJson.averageRating : undefined);
3703
+ const contentNode = {
3704
+ childCount: 2,
3705
+ className: mergeClassNames(Viewlet, List$1),
3706
+ type: Div
3769
3707
  };
3770
- const getRating = extension => {
3771
- const rating = getRatingValue(extension);
3772
- if (typeof rating !== 'number') {
3773
- return 'n/a';
3774
- }
3775
- return rating.toFixed(1);
3708
+ const extensionsNode = {
3709
+ ariaBusy: false,
3710
+ ariaLive: 'polite',
3711
+ childCount: 2,
3712
+ className: mergeClassNames(Viewlet, Extensions$1),
3713
+ role: None$3,
3714
+ type: Div
3776
3715
  };
3777
-
3778
- const getSize = extension => {
3779
- if (extension === null || typeof extension !== 'object' || !('size' in extension) || extension.size === 0 || typeof extension.size !== 'number') {
3780
- return 0;
3716
+ const getContentVirtualDom = (visibleExtensions, message, scrollBarHeight, scrollBarY, focusOutline) => {
3717
+ if (message) {
3718
+ return getNoExtensionsFoundVirtualDom(message);
3781
3719
  }
3782
- return extension.size;
3720
+ return [contentNode, ...getExtensionsVirtualDom(visibleExtensions, focusOutline), ...getScrollBarVirtualDom(scrollBarHeight)];
3783
3721
  };
3784
-
3785
- const getStatus = extension => {
3786
- if (extension === null || typeof extension !== 'object' || !('status' in extension)) {
3787
- return undefined;
3788
- }
3789
- return typeof extension.status === 'string' ? extension.status : undefined;
3722
+ const getExtensionsViewVirtualDom = state => {
3723
+ const visibleExtensions = getVisible(state);
3724
+ const {
3725
+ completionFocusedIndex,
3726
+ completionItems,
3727
+ focus,
3728
+ focusedIndex,
3729
+ inputActions,
3730
+ message,
3731
+ placeholder,
3732
+ scrollBarHeight,
3733
+ scrollBarY,
3734
+ suggestOpen
3735
+ } = state;
3736
+ const focusOutline = focusedIndex === -1 && focus === List$2;
3737
+ return [extensionsNode, ...getExtensionHeaderVirtualDom(placeholder, inputActions, completionItems, completionFocusedIndex, suggestOpen), ...getContentVirtualDom(visibleExtensions, message, scrollBarHeight, scrollBarY, focusOutline)];
3790
3738
  };
3791
3739
 
3792
- const getUpdatedDate = extension => {
3793
- if (extension === null || typeof extension !== 'object' || !('updatedDate' in extension) || !extension.updatedDate || typeof extension.updatedDate !== 'number') {
3794
- return 0;
3740
+ const renderItems2 = newState => {
3741
+ const {
3742
+ initial,
3743
+ uid
3744
+ } = newState;
3745
+ if (initial) {
3746
+ return [SetDom2, uid, []];
3795
3747
  }
3796
- return extension.updatedDate;
3748
+ const dom = getExtensionsViewVirtualDom(newState);
3749
+ return [SetDom2, uid, dom];
3797
3750
  };
3798
3751
 
3799
- const normalizeExtension = (extension, platform, assetDir) => {
3800
- return {
3801
- builtin: getBuiltin(extension),
3802
- categories: getCategories(extension),
3803
- description: getDescription(extension),
3804
- disabled: getDisabled(extension),
3805
- downloadCount: getDownloadCount(extension),
3806
- icon: getIcon(extension, platform, assetDir),
3807
- id: getId(extension),
3808
- linked: getLinked(extension),
3809
- name: getName(extension),
3810
- publisher: getPublisher(extension),
3811
- rating: getRating(extension),
3812
- size: getSize(extension),
3813
- status: getStatus(extension),
3814
- updatedDate: getUpdatedDate(extension),
3815
- uri: ''
3816
- };
3752
+ const getComponentDom = uid => {
3753
+ const state = getComponentState(uid);
3754
+ return renderItems2(state)[2];
3817
3755
  };
3818
3756
 
3819
- const normalizeExtensions = (extensions, platform, assetDir) => {
3820
- return Array.from(extensions, extension => normalizeExtension(extension, platform, assetDir));
3821
- };
3757
+ const Tab = 2;
3758
+ const Enter = 3;
3759
+ const Escape = 8;
3760
+ const Space = 9;
3761
+ const PageUp = 10;
3762
+ const PageDown = 11;
3763
+ const End = 255;
3764
+ const Home = 12;
3765
+ const UpArrow = 14;
3766
+ const DownArrow = 16;
3822
3767
 
3823
- const handleExtensionsChanged = async state => {
3824
- const {
3825
- assetDir,
3826
- platform
3827
- } = state;
3828
- const allExtensions = await getAllExtensions(assetDir, platform);
3829
- const normalized = normalizeExtensions(allExtensions, platform, assetDir);
3830
- return handleChange({
3831
- ...state,
3832
- allExtensions: normalized
3833
- }, {});
3834
- };
3768
+ const CtrlCmd = 1 << 11 >>> 0;
3835
3769
 
3836
- const handleFocus = async state => {
3837
- return {
3838
- ...state,
3839
- focus: List$2
3840
- };
3841
- };
3770
+ const FocusExtensions = 15;
3771
+ const FocusExtensionsInput = 7000;
3842
3772
 
3843
- const handleHeaderContextMenu = async state => {
3844
- return state;
3845
- };
3846
-
3847
- const handleInputFocus = state => {
3848
- return {
3849
- ...state,
3850
- focus: Input
3851
- };
3852
- };
3853
-
3854
- const handleInstall = async (state, id) => {
3855
- await installExtension(id);
3856
- return setExtensionStatus(state, id, Enabled);
3857
- };
3858
-
3859
- const state = {
3860
- connected: false
3861
- };
3862
- const isConnected = () => {
3863
- const {
3864
- connected
3865
- } = state;
3866
- return connected;
3867
- };
3868
- const invoke = (method, ...params) => {
3869
- return invoke$2(method, ...params);
3870
- };
3871
- const set = rpc => {
3872
- set$3(rpc);
3873
- state.connected = true;
3874
- };
3875
-
3876
- const handleMessagePort = async (port, viewletCommandMap, setAsRendererProcess = true) => {
3877
- const executeViewletCommand = async (uid, command, ...args) => {
3878
- const fn = viewletCommandMap[`SearchExtensions.${command}`];
3879
- if (typeof fn !== 'function') {
3880
- throw new TypeError(`Viewlet command not found: ${command}`);
3881
- }
3882
- await fn(uid, ...args);
3883
- await invoke$1('Viewlet.requestRender', uid);
3884
- };
3885
- const rpc = await create$7({
3886
- commandMap: {
3887
- 'Viewlet.executeViewletCommand': executeViewletCommand
3888
- },
3889
- messagePort: port
3890
- });
3891
- if (setAsRendererProcess) {
3892
- set(rpc);
3893
- }
3894
- };
3895
-
3896
- const handleScrollBarCaptureLost = state => {
3897
- return {
3898
- ...state,
3899
- scrollBarActive: false
3900
- };
3901
- };
3902
-
3903
- const getNewDeltaPercent = (height, scrollBarHeight, relativeY) => {
3904
- const halfScrollBarHeight = scrollBarHeight / 2;
3905
- if (relativeY <= halfScrollBarHeight) {
3906
- // clicked at top
3907
- return {
3908
- handleOffset: relativeY,
3909
- percent: 0
3910
- };
3911
- }
3912
- if (relativeY <= height - halfScrollBarHeight) {
3913
- // clicked in middle
3914
- return {
3915
- handleOffset: halfScrollBarHeight,
3916
- percent: (relativeY - halfScrollBarHeight) / (height - scrollBarHeight)
3917
- };
3918
- }
3919
- // clicked at bottom
3920
- return {
3921
- handleOffset: scrollBarHeight - height + relativeY,
3922
- percent: 1
3923
- };
3924
- };
3925
-
3926
- const clamp = (num, min, max) => {
3927
- number(num);
3928
- number(min);
3929
- number(max);
3930
- return Math.min(Math.max(num, min), max);
3773
+ const getKeyBindings = () => {
3774
+ return [{
3775
+ command: 'Extensions.closeSuggest',
3776
+ key: Escape,
3777
+ when: FocusExtensionsInput
3778
+ }, {
3779
+ command: 'Extensions.acceptCompletion',
3780
+ key: Enter,
3781
+ when: FocusExtensionsInput
3782
+ }, {
3783
+ command: 'Extensions.acceptCompletion',
3784
+ key: Tab,
3785
+ when: FocusExtensionsInput
3786
+ }, {
3787
+ command: 'Extensions.selectPreviousCompletion',
3788
+ key: UpArrow,
3789
+ when: FocusExtensionsInput
3790
+ }, {
3791
+ command: 'Extensions.selectNextCompletion',
3792
+ key: DownArrow,
3793
+ when: FocusExtensionsInput
3794
+ }, {
3795
+ command: 'Extensions.openSuggest',
3796
+ key: CtrlCmd | Space,
3797
+ when: FocusExtensionsInput
3798
+ }, {
3799
+ command: 'Extensions.focusFirst',
3800
+ key: Home,
3801
+ when: FocusExtensions
3802
+ }, {
3803
+ command: 'Extensions.focusLast',
3804
+ key: End,
3805
+ when: FocusExtensions
3806
+ }, {
3807
+ command: 'Extensions.focusPreviousPage',
3808
+ key: PageUp,
3809
+ when: FocusExtensions
3810
+ }, {
3811
+ command: 'Extensions.focusNextPage',
3812
+ key: PageDown,
3813
+ when: FocusExtensions
3814
+ }, {
3815
+ command: 'Extensions.focusPrevious',
3816
+ key: UpArrow,
3817
+ when: FocusExtensions
3818
+ }, {
3819
+ command: 'Extensions.focusNext',
3820
+ key: DownArrow,
3821
+ when: FocusExtensions
3822
+ }, {
3823
+ command: 'Extensions.handleClickCurrentButKeepFocus',
3824
+ key: Space,
3825
+ when: FocusExtensions
3826
+ }, {
3827
+ command: 'Extensions.handleClickCurrent',
3828
+ key: Enter,
3829
+ when: FocusExtensions
3830
+ }, {
3831
+ command: 'Extensions.toggleSuggest',
3832
+ key: CtrlCmd | Space,
3833
+ when: FocusExtensions
3834
+ }, {
3835
+ command: 'Extensions.scrollDown',
3836
+ key: CtrlCmd | DownArrow,
3837
+ when: FocusExtensions
3838
+ }];
3931
3839
  };
3932
3840
 
3933
- const setDeltaY = (state, value) => {
3934
- object(state);
3935
- number(value);
3936
- const {
3937
- deltaY,
3938
- finalDeltaY,
3939
- headerHeight,
3940
- height,
3941
- itemHeight,
3942
- items,
3943
- minimumSliderSize
3944
- } = state;
3945
- const listHeight = height - headerHeight;
3946
- const newDeltaY = clamp(value, 0, finalDeltaY);
3947
- if (deltaY === newDeltaY) {
3948
- return state;
3949
- }
3950
- // TODO when it only moves by one px, extensions don't need to be rerendered, only negative margin
3951
- const minLineY = Math.floor(newDeltaY / itemHeight);
3952
- const total = items.length;
3953
- const maxLineY = Math.min(minLineY + getNumberOfVisibleItems$1(listHeight, itemHeight), total);
3954
- const contentHeight = total * itemHeight;
3955
- const scrollBarHeight = getScrollBarSize(listHeight, contentHeight, minimumSliderSize);
3956
- const scrollBarY = getScrollBarY$1(newDeltaY, finalDeltaY, height - headerHeight, scrollBarHeight);
3957
- return {
3958
- ...state,
3959
- deltaY: newDeltaY,
3960
- maxLineY,
3961
- minLineY,
3962
- scrollBarY
3963
- };
3964
- };
3841
+ const Separator = 1;
3842
+ const None = 0;
3843
+ const SubMenu = 4;
3844
+ const Disabled = 5;
3965
3845
 
3966
- const handleScrollBarClick = (state, eventY) => {
3967
- // TODO move this to list
3968
- const {
3969
- deltaY,
3970
- finalDeltaY,
3971
- headerHeight,
3972
- height,
3973
- scrollBarHeight,
3974
- y
3975
- } = state;
3976
- const contentHeight = height - headerHeight;
3977
- const relativeY = eventY - y - headerHeight;
3978
- const currentScrollBarY = getScrollBarY$1(deltaY, finalDeltaY, contentHeight, scrollBarHeight);
3979
- const diff = relativeY - currentScrollBarY;
3980
- if (diff >= 0 && diff < scrollBarHeight) {
3846
+ const nonEnableableStatuses = [Installing, NotInstalled, Uninstalling];
3847
+ const getEnablementFlags = (disabled, status) => {
3848
+ if (status && nonEnableableStatuses.includes(status)) {
3981
3849
  return {
3982
- ...state,
3983
- handleOffset: diff,
3984
- scrollBarActive: true
3850
+ disable: Disabled,
3851
+ enable: Disabled
3985
3852
  };
3986
3853
  }
3987
- const {
3988
- handleOffset,
3989
- percent
3990
- } = getNewDeltaPercent(contentHeight, scrollBarHeight, relativeY);
3991
- const newDeltaY = percent * finalDeltaY;
3854
+ const isDisabled = status === Disabled$1 || status === undefined && disabled;
3992
3855
  return {
3993
- ...setDeltaY(state, newDeltaY),
3994
- handleOffset,
3995
- scrollBarActive: true
3856
+ disable: isDisabled ? Disabled : None,
3857
+ enable: isDisabled ? None : Disabled
3996
3858
  };
3997
3859
  };
3998
-
3999
- const getNewPercent = (contentHeight, scrollBarHeight, relativeY) => {
4000
- if (relativeY <= contentHeight - scrollBarHeight / 2) {
4001
- // clicked in middle
4002
- return relativeY / (contentHeight - scrollBarHeight);
4003
- }
4004
- // clicked at bottom
4005
- return 1;
4006
- };
4007
- const handleScrollBarMove = (state, eventY) => {
4008
- const {
4009
- finalDeltaY,
4010
- handleOffset,
4011
- headerHeight,
4012
- height,
4013
- scrollBarActive,
4014
- scrollBarHeight,
4015
- y
4016
- } = state;
4017
- if (!scrollBarActive) {
4018
- return state;
4019
- }
4020
- const relativeY = eventY - y - headerHeight - handleOffset;
4021
- const contentHeight = height - headerHeight;
4022
- const newPercent = getNewPercent(contentHeight, scrollBarHeight, relativeY);
4023
- const newDeltaY = newPercent * finalDeltaY;
4024
- return setDeltaY(state, newDeltaY);
3860
+ const getMenuEntriesList = (builtin, disabled = false, status) => {
3861
+ const enablementFlags = getEnablementFlags(disabled, status);
3862
+ return [{
3863
+ command: 'Extensions.enable',
3864
+ flags: enablementFlags.enable,
3865
+ id: 'enable',
3866
+ label: enable$2()
3867
+ }, {
3868
+ command: 'Extensions.enableWorkspace',
3869
+ flags: enablementFlags.enable,
3870
+ id: 'enableWorkspace',
3871
+ label: enableWorkspace$1()
3872
+ }, {
3873
+ command: '',
3874
+ flags: Separator,
3875
+ id: 'separator1',
3876
+ label: ''
3877
+ }, {
3878
+ command: 'Extensions.disable',
3879
+ flags: enablementFlags.disable,
3880
+ id: 'disable',
3881
+ label: disable$2()
3882
+ }, {
3883
+ command: 'Extensions.disableWorkspace',
3884
+ flags: enablementFlags.disable,
3885
+ id: 'disableWorkspace',
3886
+ label: disableWorkspace$1()
3887
+ }, {
3888
+ command: '',
3889
+ flags: Separator,
3890
+ id: 'separator2',
3891
+ label: ''
3892
+ }, {
3893
+ command: 'Extensions.installAnotherVersion',
3894
+ flags: Disabled,
3895
+ id: 'installAnotherVersion',
3896
+ label: installAnotherVersion$1()
3897
+ }, {
3898
+ command: 'Extensions.copyExtensionInfo',
3899
+ flags: None,
3900
+ id: 'copy',
3901
+ label: copy()
3902
+ }, {
3903
+ command: 'Extensions.copyExtensionId',
3904
+ flags: None,
3905
+ id: 'copyExtensionId',
3906
+ label: copyExtensionId$1()
3907
+ }];
4025
3908
  };
4026
3909
 
4027
- const handleSettingsButtonClick = async (state, index) => {
4028
- const {
4029
- deltaY,
4030
- headerHeight,
4031
- itemHeight,
4032
- items,
4033
- uid,
4034
- x,
4035
- y
4036
- } = state;
4037
- const actualIndex = index;
4038
- if (actualIndex < 0 || actualIndex >= items.length) {
4039
- return state;
4040
- }
4041
-
4042
- // Calculate the position for the context menu
4043
- // The settings button is at the bottom right of the extension list item
4044
- const itemY = y + headerHeight + actualIndex * itemHeight - deltaY;
4045
- const menuX = x + 200; // Position near the right side of the extension item
4046
- const menuY = itemY + itemHeight - 10; // Position at the bottom of the extension item
4047
-
4048
- await show2(uid, ManageExtension, menuX, menuY, {
4049
- builtin: items[actualIndex].builtin === true,
4050
- disabled: items[actualIndex].disabled === true,
4051
- menuId: ManageExtension,
4052
- status: items[actualIndex].status
4053
- });
4054
- return state;
3910
+ const getMenuEntriesFilter = () => {
3911
+ return [{
3912
+ command: 'Extensions.filterByFeatured',
3913
+ flags: None,
3914
+ id: 'filterByFeatured',
3915
+ label: featured()
3916
+ }, {
3917
+ command: 'Extensions.filterByMcpServers',
3918
+ flags: None,
3919
+ id: 'filterByMcpServers',
3920
+ label: mcpServers()
3921
+ }, {
3922
+ command: 'Extensions.filterByMostPopular',
3923
+ flags: None,
3924
+ id: 'filterByMostPopular',
3925
+ label: mostPopular()
3926
+ }, {
3927
+ command: 'Extensions.filterByRecentlyPublished',
3928
+ flags: None,
3929
+ id: 'filterByRecentlyPublished',
3930
+ label: recentlyPublished()
3931
+ }, {
3932
+ command: 'Extensions.filterByRecommended',
3933
+ flags: None,
3934
+ id: 'filterByRecommended',
3935
+ label: recommended()
3936
+ }, {
3937
+ command: '',
3938
+ flags: Separator,
3939
+ id: 'separator1',
3940
+ label: ''
3941
+ }, {
3942
+ command: 'SearchExtensions.filterByCategory',
3943
+ flags: SubMenu,
3944
+ id: 'filterByCategory',
3945
+ label: category()
3946
+ }, {
3947
+ command: 'Extensions.filterByInstalled',
3948
+ flags: None,
3949
+ id: 'filterByInstalled',
3950
+ label: installed()
3951
+ }, {
3952
+ command: 'Extensions.filterByUpdates',
3953
+ flags: None,
3954
+ id: 'filterByUpdates',
3955
+ label: updates()
3956
+ }, {
3957
+ command: 'Extensions.filterByBuiltin',
3958
+ flags: None,
3959
+ id: 'filterByBuiltin',
3960
+ label: builtIn()
3961
+ }, {
3962
+ command: 'Extensions.filterByLinked',
3963
+ flags: None,
3964
+ id: 'filterByLinked',
3965
+ label: linked()
3966
+ }, {
3967
+ command: 'Extensions.filterByEnabled',
3968
+ flags: None,
3969
+ id: 'filterByEnabled',
3970
+ label: enabled()
3971
+ }, {
3972
+ command: 'Extensions.filterByDisabled',
3973
+ flags: None,
3974
+ id: 'filterByDisabled',
3975
+ label: disabled()
3976
+ }, {
3977
+ command: 'Extensions.filterByWorkspaceUnsupported',
3978
+ flags: None,
3979
+ id: 'filterByWorkspaceUnsupported',
3980
+ label: workspaceUnsupported()
3981
+ }, {
3982
+ command: '',
3983
+ flags: Separator,
3984
+ id: 'separator2',
3985
+ label: ''
3986
+ }, {
3987
+ command: 'SearchExtensions.filterBySortBy',
3988
+ flags: SubMenu,
3989
+ id: 'filterBySortBy',
3990
+ label: sortBy()
3991
+ }];
4055
3992
  };
4056
3993
 
4057
- const handleUninstall = async (state, id) => {
4058
- try {
4059
- await uninstall$2(id);
4060
- return setExtensionStatus(state, id, NotInstalled);
4061
- } catch (error) {
4062
- await showErrorDialog(error);
4063
- return state;
3994
+ const getMenuEntries2 = (state, props) => {
3995
+ const {
3996
+ menuId
3997
+ } = props;
3998
+ switch (menuId) {
3999
+ case ExtensionSearchFilter:
4000
+ return getMenuEntriesFilter();
4001
+ default:
4002
+ return getMenuEntriesList(props.builtin, props.disabled, props.status);
4064
4003
  }
4065
4004
  };
4066
4005
 
4067
- const handleWheel = (state, deltaMode, deltaY) => {
4068
- number(deltaMode);
4069
- number(deltaY);
4070
- return setDeltaY(state, state.deltaY + deltaY * state.scrollSensitivity);
4006
+ const getMenuIds = () => {
4007
+ return [ManageExtension, ExtensionSearchFilter];
4071
4008
  };
4072
4009
 
4073
- const createExtensionManagementWorkerRpc = async () => {
4074
- try {
4075
- const rpc = await create$6({
4076
- commandMap: {},
4077
- send: port => sendMessagePortToExtensionManagementWorker(port, 0)
4078
- });
4079
- return rpc;
4080
- } catch (error) {
4081
- throw new VError(error, `Failed to create extension management rpc`);
4082
- }
4010
+ const handleBlur = state => {
4011
+ return {
4012
+ ...state,
4013
+ focus: None$2,
4014
+ suggestOpen: false
4015
+ };
4083
4016
  };
4084
4017
 
4085
- const initializeExtensionManagementWorker = async () => {
4086
- try {
4087
- const rpc = await createExtensionManagementWorkerRpc();
4088
- set$4(rpc);
4089
- } catch {
4090
- // ignore
4091
- }
4018
+ const getExtensionDetailUri = extensionId => {
4019
+ return `extension-detail://${extensionId}`;
4092
4020
  };
4093
4021
 
4094
- const initialize = async () => {
4095
- await initializeExtensionManagementWorker();
4022
+ const openUri = async uri => {
4023
+ return openUri$1(uri);
4096
4024
  };
4097
4025
 
4098
- const installAnotherVersion = async state => {
4099
- await invoke$4('ConfirmPrompt.prompt', 'not implemented', undefined);
4100
- return state;
4026
+ const selectIndex = (state, index) => {
4027
+ return {
4028
+ ...state,
4029
+ focusedIndex: index
4030
+ };
4101
4031
  };
4102
4032
 
4103
- const Small = 1;
4104
- const Normal = 2;
4105
- const Large = 3;
4106
-
4107
- const getViewletSize = width => {
4108
- if (width < 180) {
4109
- return Small;
4110
- }
4111
- if (width < 768) {
4112
- return Normal;
4033
+ const handleClick = async (state, index) => {
4034
+ const {
4035
+ items,
4036
+ minLineY
4037
+ } = state;
4038
+ const actualIndex = index + minLineY;
4039
+ if (actualIndex < 0 || actualIndex >= items.length) {
4040
+ return {
4041
+ ...state,
4042
+ focus: List$2,
4043
+ focusedIndex: -1
4044
+ };
4113
4045
  }
4114
- return Large;
4046
+ const extension = items[actualIndex];
4047
+ const uri = getExtensionDetailUri(extension.id);
4048
+ await openUri(uri);
4049
+ const partialNewState = selectIndex(state, actualIndex);
4050
+ const newState = {
4051
+ ...partialNewState,
4052
+ focus: List$2
4053
+ };
4054
+ return newState;
4115
4055
  };
4116
4056
 
4117
- const getIsFirefox = () => {
4118
- const globalWithNavigator = globalThis;
4119
- return globalWithNavigator.navigator?.userAgent.toLowerCase().includes('firefox') ?? false;
4057
+ const getListIndex = (eventX, eventY, x, y, deltaY, itemHeight, headerHeight) => {
4058
+ const relativeDeltaY = deltaY % itemHeight;
4059
+ const relativeY = eventY - y - headerHeight + relativeDeltaY;
4060
+ const index = Math.floor(relativeY / itemHeight);
4061
+ return index;
4120
4062
  };
4121
4063
 
4122
- const getSavedValue = savedState => {
4123
- if (savedState && typeof savedState === 'object' && 'searchValue' in savedState && typeof savedState.searchValue === 'string') {
4124
- return savedState.searchValue;
4064
+ const handleCompletionPointerDown = (state, label) => {
4065
+ if (state.completionItems.every(item => item.label !== label)) {
4066
+ return Promise.resolve(state);
4125
4067
  }
4126
- return '';
4068
+ return acceptCompletion(state, label);
4127
4069
  };
4128
- const getSavedDeltaY = savedState => {
4129
- if (savedState && typeof savedState === 'object' && 'deltaY' in savedState && typeof savedState.deltaY === 'number' && !Number.isNaN(savedState.deltaY)) {
4130
- return savedState.deltaY;
4070
+
4071
+ const handleClickAt = async (state, button, eventX, eventY, name = '') => {
4072
+ if (name.startsWith('@')) {
4073
+ return handleCompletionPointerDown(state, name);
4131
4074
  }
4132
- return 0;
4075
+ if (name) {
4076
+ return state;
4077
+ }
4078
+ if (button !== LeftClick) {
4079
+ return state;
4080
+ }
4081
+ const {
4082
+ deltaY,
4083
+ headerHeight,
4084
+ itemHeight,
4085
+ x,
4086
+ y
4087
+ } = state;
4088
+ const index = getListIndex(eventX, eventY, x, y, deltaY, itemHeight, headerHeight);
4089
+ return handleClick(state, index);
4133
4090
  };
4134
4091
 
4135
- const restoreState = savedState => {
4136
- const searchValue = getSavedValue(savedState);
4137
- const savedDeltaY = getSavedDeltaY(savedState);
4138
- return {
4139
- deltaY: savedDeltaY,
4140
- searchValue
4141
- };
4092
+ const handleClickCurrent = state => {
4093
+ const {
4094
+ focusedIndex
4095
+ } = state;
4096
+ return handleClick(state, focusedIndex);
4142
4097
  };
4143
4098
 
4144
- const loadContentWithContext = async (context, savedState) => {
4099
+ const handleClickCurrentButKeepFocus = state => {
4145
4100
  const {
4146
- uid
4147
- } = context.getState();
4148
- const loadToken = getToken(uid);
4149
- try {
4150
- const initialState = context.getState();
4151
- const {
4152
- assetDir,
4153
- platform,
4154
- width
4155
- } = initialState;
4156
- const {
4157
- deltaY,
4158
- searchValue: restoredSearchValue
4159
- } = restoreState(savedState);
4160
- const size = getViewletSize(width);
4161
- const scrollSensitivity = getIsFirefox() ? 2.5 : 1;
4162
- await context.updateState(state => {
4163
- if (!state.initial) {
4164
- return state;
4165
- }
4166
- return {
4167
- ...state,
4168
- deltaY,
4169
- initial: false,
4170
- inputSource: Script,
4171
- scrollSensitivity,
4172
- searchValue: restoredSearchValue,
4173
- size
4174
- };
4175
- });
4176
- const allExtensions = await getAllExtensions(assetDir, platform);
4177
- const normalized = normalizeExtensions(allExtensions, platform, assetDir);
4178
- await context.updateState(state => ({
4179
- ...state,
4180
- allExtensions: normalized
4181
- }));
4182
- await handleChangeWithContext(context, {}, false);
4183
- } finally {
4184
- finish(uid, loadToken);
4185
- }
4101
+ focusedIndex
4102
+ } = state;
4103
+ return handleClick(state, focusedIndex);
4186
4104
  };
4187
4105
 
4188
- const openSuggest = state => {
4189
- const completionItems = getCompletionItems(state.searchValue, state.cursorOffset);
4190
- if (completionItems.length === 0) {
4191
- return state;
4192
- }
4193
- return {
4194
- ...state,
4195
- completionFocusedIndex: 0,
4196
- completionItems,
4197
- suggestOpen: true
4198
- };
4106
+ const show2 = async (uid, menuId, x, y, args) => {
4107
+ await showContextMenu2(uid, menuId, x, y, args);
4199
4108
  };
4200
4109
 
4201
- const getCss = state => {
4110
+ const handleClickFilter = async state => {
4111
+ const {
4112
+ headerHeight,
4113
+ uid,
4114
+ width,
4115
+ x,
4116
+ y
4117
+ } = state;
4118
+ const menuX = x + width + 60;
4119
+ const menuHeight = 370;
4120
+ const menuY = y + headerHeight + menuHeight;
4121
+ await show2(uid, ExtensionSearchFilter, menuX, menuY, {
4122
+ menuId: ExtensionSearchFilter
4123
+ });
4124
+ return state;
4125
+ };
4126
+
4127
+ const handleContextMenu = async (state, button, eventX, eventY) => {
4128
+ // TODO use focused index when when context menu button is -1 (keyboard)
4202
4129
  const {
4203
- cursorOffset,
4204
4130
  deltaY,
4205
4131
  headerHeight,
4206
4132
  itemHeight,
4207
- scrollBarHeight,
4208
- scrollBarY,
4209
- width
4133
+ items,
4134
+ minLineY,
4135
+ uid,
4136
+ x,
4137
+ y
4210
4138
  } = state;
4211
- const relative = -(deltaY % itemHeight);
4212
- const roundedScrollBarY = Math.round(scrollBarY);
4213
- const maximumCompletionLeft = Math.max(8, width - 168);
4214
- const completionLeft = Math.min(Math.round(8 + cursorOffset * 7.5), maximumCompletionLeft);
4215
- const completionTop = Math.max(0, headerHeight - 10);
4216
- return `.Extensions .ScrollBarThumb {
4217
- height: ${scrollBarHeight}px;
4218
- translate: 0 ${roundedScrollBarY}px;
4219
- }
4220
-
4221
-
4222
- /* TODO: avoid using negative margin. find a better way*/
4223
- .ExtensionListItem:nth-child(1) {
4224
- margin-top: ${relative}px;
4225
- }
4139
+ const visibleIndex = getListIndex(eventX, eventY, x, y, deltaY, itemHeight, headerHeight);
4140
+ const index = visibleIndex + minLineY;
4141
+ if (index < 0 || index >= items.length) {
4142
+ return state;
4143
+ }
4144
+ const item = items[index];
4145
+ await show2(uid, ManageExtension, eventX, eventY, {
4146
+ builtin: item.builtin === true,
4147
+ disabled: item.disabled === true,
4148
+ menuId: ManageExtension,
4149
+ status: item.status
4150
+ });
4151
+ return {
4152
+ ...state,
4153
+ focusedIndex: index
4154
+ };
4155
+ };
4226
4156
 
4227
- .ExtensionListItem {
4228
- box-sizing: border-box;
4229
- position: relative !important;
4230
- flex-shrink: 0;
4231
- }
4157
+ const handleDisableWorkspace = (state, id) => {
4158
+ return state;
4159
+ };
4232
4160
 
4233
- .ExtensionListItemLinkedIcon {
4234
- position: absolute;
4235
- top: 6px;
4236
- right: 6px;
4237
- width: 14px;
4238
- height: 14px;
4239
- color: var(--WorkbenchForeground, rgb(188, 190, 190));
4240
- opacity: 0.8;
4241
- }
4161
+ const handleEnableWorkspace = (state, id) => {
4162
+ return state;
4163
+ };
4242
4164
 
4243
- .ExtensionListItemDisabled:not(.ExtensionActive) {
4244
- background: color-mix(in srgb, var(--SideBarBackground, rgb(30, 35, 36)) 95%, black);
4245
- color: var(--ExtensionDisabledForeground, color-mix(in srgb, var(--WorkbenchForeground) 70%, black));
4246
- }
4165
+ const Web = 1;
4166
+ const Electron = 2;
4167
+ const Remote = 3;
4247
4168
 
4248
- .Extensions .ListItems {
4249
- display: flex;
4250
- flex-direction: column;
4251
- gap: 0;
4252
- overflow-y: hidden;
4253
- }
4169
+ const getAllExtensions$1 = async (assetDir, platform) => {
4170
+ try {
4171
+ return await invoke$3('Extensions.getAllExtensions', assetDir, platform);
4172
+ } catch (error) {
4173
+ if (platform === Web) {
4174
+ return [];
4175
+ }
4176
+ throw error;
4177
+ }
4178
+ };
4254
4179
 
4255
- .ExtensionHeader {
4256
- contain: layout style;
4257
- position: relative;
4258
- z-index: 1;
4259
- }
4180
+ const getAllExtensions = (assetDir, platform) => {
4181
+ return getAllExtensions$1(assetDir, platform);
4182
+ };
4260
4183
 
4261
- .ExtensionListItemFooter {
4262
- justify-content: flex-end;
4263
- padding-right: 2px;
4264
- }
4184
+ const getBuiltin = extension => {
4185
+ if (extension === null || typeof extension !== 'object') {
4186
+ return false;
4187
+ }
4188
+ const {
4189
+ builtin,
4190
+ id,
4191
+ isBuiltin
4192
+ } = extension;
4193
+ return isBuiltin === true || builtin === true || typeof id === 'string' && id.startsWith('builtin.');
4194
+ };
4265
4195
 
4266
- .ExtensionListItemAuthorName {
4267
- flex: 1;
4268
- }
4196
+ const isString = item => {
4197
+ return typeof item === 'string';
4198
+ };
4269
4199
 
4270
- .ExtensionActions {
4271
- display: flex;
4272
- gap: 6px;
4273
- }
4200
+ const getCategories = extension => {
4201
+ if (extension === null || typeof extension !== 'object' || !('categories' in extension) || !Array.isArray(extension.categories)) {
4202
+ return [];
4203
+ }
4204
+ return extension.categories.filter(isString);
4205
+ };
4274
4206
 
4275
- .ExtensionActionButton {
4276
- padding: 0 5px;
4277
- }
4207
+ const getDescription = extension => {
4208
+ if (extension === null || typeof extension !== 'object' || !('description' in extension) || typeof extension.description !== 'string' || !extension.description) {
4209
+ return 'n/a';
4210
+ }
4211
+ return extension.description;
4212
+ };
4278
4213
 
4279
- .ExtensionSearchCompletionWidget {
4280
- position: absolute;
4281
- left: ${completionLeft}px;
4282
- top: ${completionTop}px;
4283
- width: min(320px, calc(100% - ${completionLeft + 8}px));
4284
- max-height: 240px;
4285
- overflow-y: auto;
4286
- z-index: 10;
4287
- box-sizing: border-box;
4288
- border: 1px solid var(--CompletionListBorder, #95a29d);
4289
- background: var(--CompletionListBackground, #282e2f);
4290
- color: var(--CompletionListForeground, white);
4291
- box-shadow: 0 2px 8px rgba(0, 0, 0, 0.36);
4292
- font-size: 13px;
4293
- line-height: 20px;
4294
- user-select: none;
4295
- }
4214
+ const getDisabled = extension => {
4215
+ return extension !== null && typeof extension === 'object' && 'disabled' in extension && extension.disabled === true;
4216
+ };
4296
4217
 
4297
- .ExtensionSearchCompletionItem {
4298
- display: block;
4299
- width: 100%;
4300
- min-height: 20px;
4301
- padding: 0 6px;
4302
- border: 0;
4303
- background: transparent;
4304
- color: inherit;
4305
- font: inherit;
4306
- line-height: inherit;
4307
- text-align: left;
4308
- overflow: hidden;
4309
- text-overflow: ellipsis;
4310
- white-space: nowrap;
4311
- cursor: pointer;
4312
- }
4218
+ const getDownloadCountValue = extension => {
4219
+ if (extension === null || typeof extension !== 'object') {
4220
+ return undefined;
4221
+ }
4222
+ const marketplace = 'marketplace' in extension && extension.marketplace !== null && typeof extension.marketplace === 'object' ? extension.marketplace : {};
4223
+ const packageJson = 'packageJSON' in extension && extension.packageJSON !== null && typeof extension.packageJSON === 'object' ? extension.packageJSON : {};
4224
+ return ('downloadCount' in extension ? extension.downloadCount : undefined) ?? ('downloads' in extension ? extension.downloads : undefined) ?? ('downloadCount' in marketplace ? marketplace.downloadCount : undefined) ?? ('downloads' in marketplace ? marketplace.downloads : undefined) ?? ('downloadCount' in packageJson ? packageJson.downloadCount : undefined) ?? ('downloads' in packageJson ? packageJson.downloads : undefined);
4225
+ };
4226
+ const getDownloadCount = extension => {
4227
+ const downloadCount = getDownloadCountValue(extension);
4228
+ if (typeof downloadCount !== 'number') {
4229
+ return 'n/a';
4230
+ }
4231
+ return downloadCount.toLocaleString();
4232
+ };
4313
4233
 
4314
- .ExtensionSearchCompletionItem:hover {
4315
- background: var(--CompletionListItemHoverBackground, rgba(64, 92, 80, 0.2));
4316
- }
4234
+ const getRemoteUrl = (extension, platform, assetDir) => {
4235
+ if (extension === null || typeof extension !== 'object') {
4236
+ return '';
4237
+ }
4238
+ const builtin = 'builtin' in extension && extension.builtin === true;
4239
+ const icon = 'icon' in extension && typeof extension.icon === 'string' ? extension.icon : '';
4240
+ const id = 'id' in extension && typeof extension.id === 'string' ? extension.id : '';
4241
+ const path = 'path' in extension && typeof extension.path === 'string' ? extension.path : '';
4242
+ if (platform === Remote || platform === Electron) {
4243
+ if (builtin) {
4244
+ return `${assetDir}/extensions/${id}/${icon}`;
4245
+ }
4246
+ return `/remote/${path}/${icon}`; // TODO support windows paths
4247
+ }
4248
+ if (platform === Web) {
4249
+ return `${path}/${icon}`;
4250
+ }
4251
+ return '';
4252
+ };
4317
4253
 
4318
- .ExtensionSearchCompletionItemFocused {
4319
- background: var(--CompletionListItemActiveBackground, #405c50);
4320
- color: var(--CompletionListItemActiveForeground);
4321
- }
4254
+ const getExtensionDefaultIcon = assetDir => {
4255
+ return `${assetDir}/icons/extensionDefaultIcon.png`;
4256
+ };
4257
+ const getExtensionLanguageBasicsIcon = assetDir => {
4258
+ return `${assetDir}/icons/language-icon.svg`;
4259
+ };
4260
+ const getExtensionThemeIcon = assetDir => {
4261
+ return `${assetDir}/icons/theme-icon.png`;
4262
+ };
4322
4263
 
4323
- .ExtensionSearchCompletionHighlight {
4324
- color: var(--CompletionHighlightForeground, #e1b974);
4325
- font-weight: 700;
4326
- }
4327
- `;
4264
+ const isLanguageBasicsExtension = extension => {
4265
+ return 'name' in extension && typeof extension.name === 'string' && extension.name.startsWith('Language Basics');
4266
+ };
4267
+ const isThemeExtension = extension => {
4268
+ return 'name' in extension && typeof extension.name === 'string' && extension.name.endsWith(' Theme');
4269
+ };
4270
+ const getExtensionIcon = (extension, platform, assetDir) => {
4271
+ if (extension === null || typeof extension !== 'object') {
4272
+ return getExtensionDefaultIcon(assetDir);
4273
+ }
4274
+ const hasIcon = 'icon' in extension && typeof extension.icon === 'string' && extension.icon;
4275
+ const hasPath = 'path' in extension && typeof extension.path === 'string' && extension.path;
4276
+ if (!hasPath || !hasIcon) {
4277
+ if (isLanguageBasicsExtension(extension)) {
4278
+ return getExtensionLanguageBasicsIcon(assetDir);
4279
+ }
4280
+ if (isThemeExtension(extension)) {
4281
+ return getExtensionThemeIcon(assetDir);
4282
+ }
4283
+ return getExtensionDefaultIcon(assetDir);
4284
+ }
4285
+ return getRemoteUrl(extension, platform, assetDir);
4328
4286
  };
4329
4287
 
4330
- const renderCss = newState => {
4331
- const {
4332
- uid
4333
- } = newState;
4334
- const css = getCss(newState);
4335
- return [SetCss, uid, css];
4288
+ const getIcon = (extension, platform, assetDir) => {
4289
+ return getExtensionIcon(extension, platform, assetDir);
4336
4290
  };
4337
4291
 
4338
- const Extensions$1 = 'extensions';
4292
+ const getId = extension => {
4293
+ if (extension === null || typeof extension !== 'object' || !('id' in extension) || typeof extension.id !== 'string' || !extension.id) {
4294
+ return 'n/a';
4295
+ }
4296
+ return extension.id;
4297
+ };
4339
4298
 
4340
- const getSelector = focus => {
4341
- switch (focus) {
4342
- case Input:
4343
- return `[name="${Extensions$1}"]`;
4344
- case List$2:
4345
- return '.ListItems';
4346
- default:
4347
- return '';
4299
+ const getLinked = extension => {
4300
+ if (extension === null || typeof extension !== 'object') {
4301
+ return false;
4302
+ }
4303
+ if ('linked' in extension && extension.linked === true) {
4304
+ return true;
4348
4305
  }
4306
+ return 'symlink' in extension && typeof extension.symlink === 'string' && extension.symlink.length > 0;
4349
4307
  };
4350
- const renderFocus = newState => {
4351
- const {
4352
- focus,
4353
- uid
4354
- } = newState;
4355
- if (!focus) {
4356
- return [];
4308
+
4309
+ const getName = extension => {
4310
+ if (extension === null || typeof extension !== 'object') {
4311
+ return 'n/a';
4312
+ }
4313
+ if ('name' in extension && typeof extension.name === 'string' && extension.name) {
4314
+ return extension.name;
4357
4315
  }
4358
- const selector = getSelector(focus);
4359
- return [FocusSelector, uid, selector];
4316
+ if ('id' in extension && typeof extension.id === 'string' && extension.id) {
4317
+ return extension.id;
4318
+ }
4319
+ return 'n/a';
4360
4320
  };
4361
4321
 
4362
- const renderFocusContext = newState => {
4363
- const {
4364
- uid
4365
- } = newState;
4366
- if (newState.focus === Input) {
4367
- return ['Viewlet.setFocusContext', uid, FocusExtensionsInput];
4322
+ const RE_PUBLISHER = /^[a-z\d-]+/;
4323
+ const getPublisher = extension => {
4324
+ if (extension === null || typeof extension !== 'object' || !('id' in extension) || typeof extension.id !== 'string') {
4325
+ return 'n/a';
4368
4326
  }
4369
- if (newState.focus === List$2) {
4370
- return ['Viewlet.setFocusContext', uid, FocusExtensions];
4327
+ const match = extension.id.match(RE_PUBLISHER);
4328
+ if (!match) {
4329
+ return 'n/a';
4371
4330
  }
4372
- return [];
4331
+ return match[0];
4373
4332
  };
4374
4333
 
4375
- const ComboBox = 'combobox';
4376
- const Image = 'img';
4377
- const List$1 = 'list';
4378
- const ListBox = 'listbox';
4379
- const ListItem = 'listitem';
4380
- const None = 'none';
4381
- const Option = 'option';
4382
- const ToolBar = 'toolbar';
4383
-
4384
- const Actions = 'Actions';
4385
- const ExtensionActions = 'ExtensionActions';
4386
- const ExtensionActionButton = 'ExtensionActionButton';
4387
- const ExtensionActive = 'ExtensionActive';
4388
- const ExtensionHeader = 'ExtensionHeader';
4389
- const ExtensionListItem = 'ExtensionListItem';
4390
- const ExtensionListItemActionInstall = 'ExtensionListItemActionInstall';
4391
- const ExtensionListItemAuthorName = 'ExtensionListItemAuthorName';
4392
- const ExtensionListItemDescription = 'ExtensionListItemDescription';
4393
- const ExtensionListItemDetail = 'ExtensionListItemDetail';
4394
- const ExtensionListItemDisabled = 'ExtensionListItemDisabled';
4395
- const ExtensionListItemDownloadCount = 'ExtensionListItemDownloadCount';
4396
- const ExtensionListItemFooter = 'ExtensionListItemFooter';
4397
- const ExtensionListItemIcon = 'ExtensionListItemIcon';
4398
- const ExtensionListItemLinkedIcon = 'ExtensionListItemLinkedIcon';
4399
- const ExtensionListItemMetadata = 'ExtensionListItemMetadata';
4400
- const ExtensionListItemName = 'ExtensionListItemName';
4401
- const ExtensionListItemRating = 'ExtensionListItemRating';
4402
- const ExtensionListItemStatistic = 'ExtensionListItemStatistic';
4403
- const ExtensionSearchCompletionHighlight = 'ExtensionSearchCompletionHighlight';
4404
- const ExtensionSearchCompletionItem = 'ExtensionSearchCompletionItem';
4405
- const ExtensionSearchCompletionItemFocused = 'ExtensionSearchCompletionItemFocused';
4406
- const ExtensionSearchCompletionWidget = 'ExtensionSearchCompletionWidget';
4407
- const Extensions = 'Extensions';
4408
- const FocusOutline = 'FocusOutline';
4409
- const IconButton = 'IconButton';
4410
- const List = 'List';
4411
- const ListItems = 'ListItems';
4412
- const MaskIcon = 'MaskIcon';
4413
- const MultilineInputBox = 'MultilineInputBox';
4414
- const NoExtensionsFoundMessage = 'NoExtensionsFoundMessage';
4415
- const ScrollBar = 'ScrollBar';
4416
- const ScrollBarSmall = 'ScrollBarSmall';
4417
- const ScrollBarThumb = 'ScrollBarThumb';
4418
- const ScrollBarThumbActive = 'ScrollBarThumbActive';
4419
- const SearchField = 'SearchField';
4420
- const SearchFieldButton = 'SearchFieldButton';
4421
- const SearchFieldButtonDisabled = 'SearchFieldButtonDisabled';
4422
- const SearchFieldButtons = 'SearchFieldButtons';
4423
- const SearchFieldContainer = 'SearchFieldContainer';
4424
- const Viewlet = 'Viewlet';
4425
-
4426
- const getCompletionLabelVirtualDom = (label, highlights) => {
4427
- const dom = [];
4428
- let position = 0;
4429
- for (let i = 0; i < highlights.length; i += 2) {
4430
- const start = highlights[i];
4431
- const end = highlights[i + 1];
4432
- if (position < start) {
4433
- dom.push(text(label.slice(position, start)));
4434
- }
4435
- dom.push({
4436
- childCount: 1,
4437
- className: ExtensionSearchCompletionHighlight,
4438
- name: label,
4439
- type: Span
4440
- }, text(label.slice(start, end)));
4441
- position = end;
4334
+ const getRatingValue = extension => {
4335
+ if (extension === null || typeof extension !== 'object') {
4336
+ return undefined;
4442
4337
  }
4443
- if (position < label.length) {
4444
- dom.push(text(label.slice(position)));
4338
+ const marketplace = 'marketplace' in extension && extension.marketplace !== null && typeof extension.marketplace === 'object' ? extension.marketplace : {};
4339
+ const packageJson = 'packageJSON' in extension && extension.packageJSON !== null && typeof extension.packageJSON === 'object' ? extension.packageJSON : {};
4340
+ return ('rating' in extension ? extension.rating : undefined) ?? ('averageRating' in extension ? extension.averageRating : undefined) ?? ('rating' in marketplace ? marketplace.rating : undefined) ?? ('averageRating' in marketplace ? marketplace.averageRating : undefined) ?? ('rating' in packageJson ? packageJson.rating : undefined) ?? ('averageRating' in packageJson ? packageJson.averageRating : undefined);
4341
+ };
4342
+ const getRating = extension => {
4343
+ const rating = getRatingValue(extension);
4344
+ if (typeof rating !== 'number') {
4345
+ return 'n/a';
4445
4346
  }
4446
- return dom;
4347
+ return rating.toFixed(1);
4447
4348
  };
4448
4349
 
4449
- const getRootNodeCount = nodes => {
4450
- let count = 0;
4451
- const remainingChildCounts = [];
4452
- for (const node of nodes) {
4453
- while (remainingChildCounts.length > 0 && remainingChildCounts.at(-1) === 0) {
4454
- remainingChildCounts.pop();
4455
- }
4456
- if (remainingChildCounts.length === 0) {
4457
- count++;
4458
- } else {
4459
- const lastIndex = remainingChildCounts.length - 1;
4460
- remainingChildCounts[lastIndex]--;
4461
- }
4462
- remainingChildCounts.push(node.childCount);
4350
+ const getSize = extension => {
4351
+ if (extension === null || typeof extension !== 'object' || !('size' in extension) || extension.size === 0 || typeof extension.size !== 'number') {
4352
+ return 0;
4463
4353
  }
4464
- return count;
4354
+ return extension.size;
4465
4355
  };
4466
- const getCompletionItemVirtualDom = (item, index, focusedIndex) => {
4467
- const labelDom = getCompletionLabelVirtualDom(item.label, item.highlights);
4468
- const focused = index === focusedIndex;
4469
- return [{
4470
- ariaSelected: focused,
4471
- childCount: getRootNodeCount(labelDom),
4472
- className: focused ? mergeClassNames(ExtensionSearchCompletionItem, ExtensionSearchCompletionItemFocused) : ExtensionSearchCompletionItem,
4473
- id: `ExtensionSearchCompletion-${index}`,
4474
- name: item.label,
4475
- onPointerDown: HandlePointerDown,
4476
- role: Option,
4477
- type: Button$2
4478
- }, ...labelDom];
4356
+
4357
+ const getStatus = extension => {
4358
+ if (extension === null || typeof extension !== 'object' || !('status' in extension)) {
4359
+ return undefined;
4360
+ }
4361
+ return typeof extension.status === 'string' ? extension.status : undefined;
4479
4362
  };
4480
4363
 
4481
- const getCompletionWidgetVirtualDom = (items, focusedIndex) => {
4482
- return [{
4483
- ariaLabel: 'Extension search completions',
4484
- childCount: items.length,
4485
- className: ExtensionSearchCompletionWidget,
4486
- id: 'ExtensionSearchCompletions',
4487
- role: ListBox,
4488
- type: Div
4489
- }, ...items.flatMap((item, index) => getCompletionItemVirtualDom(item, index, focusedIndex))];
4364
+ const getUpdatedDate = extension => {
4365
+ if (extension === null || typeof extension !== 'object' || !('updatedDate' in extension) || !extension.updatedDate || typeof extension.updatedDate !== 'number') {
4366
+ return 0;
4367
+ }
4368
+ return extension.updatedDate;
4490
4369
  };
4491
4370
 
4492
- const Focusable = 0;
4371
+ const normalizeExtension = (extension, platform, assetDir) => {
4372
+ return {
4373
+ builtin: getBuiltin(extension),
4374
+ categories: getCategories(extension),
4375
+ description: getDescription(extension),
4376
+ disabled: getDisabled(extension),
4377
+ downloadCount: getDownloadCount(extension),
4378
+ icon: getIcon(extension, platform, assetDir),
4379
+ id: getId(extension),
4380
+ linked: getLinked(extension),
4381
+ name: getName(extension),
4382
+ publisher: getPublisher(extension),
4383
+ rating: getRating(extension),
4384
+ size: getSize(extension),
4385
+ status: getStatus(extension),
4386
+ updatedDate: getUpdatedDate(extension),
4387
+ uri: ''
4388
+ };
4389
+ };
4493
4390
 
4494
- const disabledClassName = mergeClassNames(SearchFieldButton, SearchFieldButtonDisabled);
4495
- const getClassName = enabled => {
4496
- if (enabled) {
4497
- return SearchFieldButton;
4498
- }
4499
- return disabledClassName;
4391
+ const normalizeExtensions = (extensions, platform, assetDir) => {
4392
+ return Array.from(extensions, extension => normalizeExtension(extension, platform, assetDir));
4500
4393
  };
4501
- const getSearchFieldButtonVirtualDom = button => {
4394
+
4395
+ const handleExtensionsChanged = async state => {
4502
4396
  const {
4503
- enabled,
4504
- icon,
4505
- onClick,
4506
- title
4507
- } = button;
4508
- return [{
4509
- childCount: 1,
4510
- className: getClassName(enabled),
4511
- onClick,
4512
- tabIndex: Focusable,
4513
- title,
4514
- type: Button$2
4515
- }, {
4516
- childCount: 0,
4517
- className: mergeClassNames(MaskIcon, icon),
4518
- type: Div
4519
- }];
4397
+ assetDir,
4398
+ platform
4399
+ } = state;
4400
+ const allExtensions = await getAllExtensions(assetDir, platform);
4401
+ const normalized = normalizeExtensions(allExtensions, platform, assetDir);
4402
+ return handleChange({
4403
+ ...state,
4404
+ allExtensions: normalized
4405
+ }, {});
4520
4406
  };
4521
4407
 
4522
- const searchFieldNode = {
4523
- childCount: 2,
4524
- className: SearchField,
4525
- role: None,
4526
- type: Div
4408
+ const handleFocus = async state => {
4409
+ return {
4410
+ ...state,
4411
+ focus: List$2
4412
+ };
4527
4413
  };
4528
- const getSearchFieldVirtualDom = (name, placeholder, onInput, insideButtons, outsideButtons, onFocus = '', onBlur = '', inputProperties = {}) => {
4529
- // TODO avoid mutation
4530
- const dom = [searchFieldNode, {
4531
- autocapitalize: 'off',
4532
- autocomplete: 'off',
4533
- autocorrect: 'off',
4534
- childCount: 0,
4535
- className: MultilineInputBox,
4536
- inputType: 'search',
4537
- name,
4538
- ...(onBlur && {
4539
- onBlur
4540
- }),
4541
- onFocus,
4542
- onInput,
4543
- placeholder,
4544
- spellcheck: false,
4545
- type: Input$1,
4546
- ...inputProperties
4547
- }, {
4548
- childCount: insideButtons.length,
4549
- className: SearchFieldButtons,
4550
- type: Div
4551
- }, ...insideButtons.flatMap(getSearchFieldButtonVirtualDom)];
4552
- if (outsideButtons.length > 0) {
4553
- dom.unshift({
4554
- childCount: 1 + outsideButtons.length,
4555
- className: SearchFieldContainer,
4556
- role: None,
4557
- type: Div
4558
- });
4559
- dom.push(...outsideButtons.flatMap(getSearchFieldButtonVirtualDom));
4560
- }
4561
- return dom;
4414
+
4415
+ const handleHeaderContextMenu = async state => {
4416
+ return state;
4562
4417
  };
4563
4418
 
4564
- const getCompletionVirtualDom = (completionItems, completionFocusedIndex, suggestOpen) => {
4565
- if (!suggestOpen) {
4566
- return [];
4567
- }
4568
- return getCompletionWidgetVirtualDom(completionItems, completionFocusedIndex);
4569
- };
4570
- const getExtensionHeaderVirtualDom = (placeholder, actions, completionItems = [], completionFocusedIndex = 0, suggestOpen = false) => {
4571
- const inputProperties = suggestOpen ? {
4572
- ariaActivedescendant: `ExtensionSearchCompletion-${completionFocusedIndex}`,
4573
- ariaAutoComplete: 'list',
4574
- ariaControls: 'ExtensionSearchCompletions',
4575
- ariaExpanded: true,
4576
- role: ComboBox
4577
- } : {
4578
- ariaAutoComplete: 'list',
4579
- ariaExpanded: false,
4580
- role: ComboBox
4419
+ const handleInputFocus = state => {
4420
+ return {
4421
+ ...state,
4422
+ focus: Input
4581
4423
  };
4582
- const completionDom = getCompletionVirtualDom(completionItems, completionFocusedIndex, suggestOpen);
4583
- return [{
4584
- childCount: suggestOpen ? 2 : 1,
4585
- className: ExtensionHeader,
4586
- onContextMenu: HandleHeaderContextMenu,
4587
- type: Div
4588
- }, ...getSearchFieldVirtualDom(Extensions$1, placeholder, HandleExtensionsInput, actions, [], HandleInputFocus, HandleInputBlur, inputProperties), ...completionDom];
4589
4424
  };
4590
4425
 
4591
- const renderHeader = newState => {
4592
- const actions = getInputActions(newState.searchValue.length > 0);
4593
- const dom = getExtensionHeaderVirtualDom(newState.placeholder, actions, newState.completionItems, newState.completionFocusedIndex, newState.suggestOpen);
4594
- return ['setHeaderDom', dom];
4426
+ const handleInstall = async (state, id) => {
4427
+ await installExtension(id);
4428
+ return setExtensionStatus(state, id, Enabled);
4595
4429
  };
4596
4430
 
4597
- const Extension = 'Extension';
4598
-
4599
- const install = {
4600
- disabled: false,
4601
- label: install$1(),
4602
- onClick: HandleInstall
4431
+ const state = {
4432
+ connected: false
4603
4433
  };
4604
- const installing = {
4605
- disabled: true,
4606
- label: installing$1(),
4607
- onClick: HandleInstall
4434
+ const isConnected = () => {
4435
+ const {
4436
+ connected
4437
+ } = state;
4438
+ return connected;
4608
4439
  };
4609
- const enable = {
4610
- disabled: false,
4611
- label: enable$2(),
4612
- onClick: HandleEnable
4440
+ const invoke = (method, ...params) => {
4441
+ return invoke$2(method, ...params);
4613
4442
  };
4614
- const disable = {
4615
- disabled: false,
4616
- label: disable$2(),
4617
- onClick: HandleDisable
4443
+ const set = rpc => {
4444
+ set$3(rpc);
4445
+ state.connected = true;
4618
4446
  };
4619
- const uninstall = {
4620
- disabled: false,
4621
- label: uninstall$1(),
4622
- onClick: HandleUninstall
4447
+
4448
+ const handleMessagePort = async (port, viewletCommandMap, setAsRendererProcess = true) => {
4449
+ const executeViewletCommand = async (uid, command, ...args) => {
4450
+ const fn = viewletCommandMap[`SearchExtensions.${command}`];
4451
+ if (typeof fn !== 'function') {
4452
+ throw new TypeError(`Viewlet command not found: ${command}`);
4453
+ }
4454
+ await fn(uid, ...args);
4455
+ await invoke$1('Viewlet.requestRender', uid);
4456
+ };
4457
+ const rpc = await create$7({
4458
+ commandMap: {
4459
+ 'Viewlet.executeViewletCommand': executeViewletCommand
4460
+ },
4461
+ messagePort: port
4462
+ });
4463
+ if (setAsRendererProcess) {
4464
+ set(rpc);
4465
+ }
4623
4466
  };
4624
- const uninstalling = {
4625
- disabled: true,
4626
- label: uninstalling$1(),
4627
- onClick: HandleUninstall
4467
+
4468
+ const handleScrollBarCaptureLost = state => {
4469
+ return {
4470
+ ...state,
4471
+ scrollBarActive: false
4472
+ };
4628
4473
  };
4629
- const getExtensionActions = (builtin, disabled, status) => {
4630
- if (status === NotInstalled) {
4631
- return [install];
4632
- }
4633
- if (status === Installing) {
4634
- return [installing];
4474
+
4475
+ const getNewDeltaPercent = (height, scrollBarHeight, relativeY) => {
4476
+ const halfScrollBarHeight = scrollBarHeight / 2;
4477
+ if (relativeY <= halfScrollBarHeight) {
4478
+ // clicked at top
4479
+ return {
4480
+ handleOffset: relativeY,
4481
+ percent: 0
4482
+ };
4635
4483
  }
4636
- if (status === Uninstalling) {
4637
- return builtin ? [] : [uninstalling];
4484
+ if (relativeY <= height - halfScrollBarHeight) {
4485
+ // clicked in middle
4486
+ return {
4487
+ handleOffset: halfScrollBarHeight,
4488
+ percent: (relativeY - halfScrollBarHeight) / (height - scrollBarHeight)
4489
+ };
4638
4490
  }
4639
- const enableOrDisable = isExtensionDisabled(disabled, status) ? enable : disable;
4640
- return builtin ? [enableOrDisable] : [enableOrDisable, uninstall];
4491
+ // clicked at bottom
4492
+ return {
4493
+ handleOffset: scrollBarHeight - height + relativeY,
4494
+ percent: 1
4495
+ };
4641
4496
  };
4642
4497
 
4643
- const className = mergeClassNames(ExtensionListItemActionInstall, ExtensionActionButton);
4644
- const getExtensionActionVirtualDom = (action, id) => {
4645
- return [{
4646
- childCount: 1,
4647
- className,
4648
- disabled: action.disabled,
4649
- name: id,
4650
- onClick: action.onClick,
4651
- type: Button$2
4652
- }, text(action.label)];
4498
+ const clamp = (num, min, max) => {
4499
+ number(num);
4500
+ number(min);
4501
+ number(max);
4502
+ return Math.min(Math.max(num, min), max);
4653
4503
  };
4654
4504
 
4655
- const getExtensionActionsVirtualDom = (id, builtin, disabled, status) => {
4656
- const actions = getExtensionActions(builtin, disabled, status);
4657
- return [{
4658
- childCount: actions.length,
4659
- className: ExtensionActions,
4660
- type: Div
4661
- }, ...actions.flatMap(action => getExtensionActionVirtualDom(action, id))];
4505
+ const setDeltaY = (state, value) => {
4506
+ object(state);
4507
+ number(value);
4508
+ const {
4509
+ deltaY,
4510
+ finalDeltaY,
4511
+ headerHeight,
4512
+ height,
4513
+ itemHeight,
4514
+ items,
4515
+ minimumSliderSize
4516
+ } = state;
4517
+ const listHeight = height - headerHeight;
4518
+ const newDeltaY = clamp(value, 0, finalDeltaY);
4519
+ if (deltaY === newDeltaY) {
4520
+ return state;
4521
+ }
4522
+ // TODO when it only moves by one px, extensions don't need to be rerendered, only negative margin
4523
+ const minLineY = Math.floor(newDeltaY / itemHeight);
4524
+ const total = items.length;
4525
+ const maxLineY = Math.min(minLineY + getNumberOfVisibleItems$1(listHeight, itemHeight), total);
4526
+ const contentHeight = total * itemHeight;
4527
+ const scrollBarHeight = getScrollBarSize(listHeight, contentHeight, minimumSliderSize);
4528
+ const scrollBarY = getScrollBarY$1(newDeltaY, finalDeltaY, height - headerHeight, scrollBarHeight);
4529
+ return {
4530
+ ...state,
4531
+ deltaY: newDeltaY,
4532
+ maxLineY,
4533
+ minLineY,
4534
+ scrollBarY
4535
+ };
4662
4536
  };
4663
4537
 
4664
- const getExtensionListItemClassName = (focused, disabled) => {
4665
- return mergeClassNames(ExtensionListItem, focused ? ExtensionActive : '', disabled ? ExtensionListItemDisabled : '');
4538
+ const handleScrollBarClick = (state, eventY) => {
4539
+ // TODO move this to list
4540
+ const {
4541
+ deltaY,
4542
+ finalDeltaY,
4543
+ headerHeight,
4544
+ height,
4545
+ scrollBarHeight,
4546
+ y
4547
+ } = state;
4548
+ const contentHeight = height - headerHeight;
4549
+ const relativeY = eventY - y - headerHeight;
4550
+ const currentScrollBarY = getScrollBarY$1(deltaY, finalDeltaY, contentHeight, scrollBarHeight);
4551
+ const diff = relativeY - currentScrollBarY;
4552
+ if (diff >= 0 && diff < scrollBarHeight) {
4553
+ return {
4554
+ ...state,
4555
+ handleOffset: diff,
4556
+ scrollBarActive: true
4557
+ };
4558
+ }
4559
+ const {
4560
+ handleOffset,
4561
+ percent
4562
+ } = getNewDeltaPercent(contentHeight, scrollBarHeight, relativeY);
4563
+ const newDeltaY = percent * finalDeltaY;
4564
+ return {
4565
+ ...setDeltaY(state, newDeltaY),
4566
+ handleOffset,
4567
+ scrollBarActive: true
4568
+ };
4569
+ };
4570
+
4571
+ const getNewPercent = (contentHeight, scrollBarHeight, relativeY) => {
4572
+ if (relativeY <= contentHeight - scrollBarHeight / 2) {
4573
+ // clicked in middle
4574
+ return relativeY / (contentHeight - scrollBarHeight);
4575
+ }
4576
+ // clicked at bottom
4577
+ return 1;
4578
+ };
4579
+ const handleScrollBarMove = (state, eventY) => {
4580
+ const {
4581
+ finalDeltaY,
4582
+ handleOffset,
4583
+ headerHeight,
4584
+ height,
4585
+ scrollBarActive,
4586
+ scrollBarHeight,
4587
+ y
4588
+ } = state;
4589
+ if (!scrollBarActive) {
4590
+ return state;
4591
+ }
4592
+ const relativeY = eventY - y - headerHeight - handleOffset;
4593
+ const contentHeight = height - headerHeight;
4594
+ const newPercent = getNewPercent(contentHeight, scrollBarHeight, relativeY);
4595
+ const newDeltaY = newPercent * finalDeltaY;
4596
+ return setDeltaY(state, newDeltaY);
4666
4597
  };
4667
4598
 
4668
- const getExtensionListItemFooter = hasStatistics => {
4669
- return {
4670
- childCount: hasStatistics ? 3 : 2,
4671
- className: ExtensionListItemFooter,
4672
- type: Div
4673
- };
4599
+ const handleSettingsButtonClick = async (state, index) => {
4600
+ const {
4601
+ deltaY,
4602
+ headerHeight,
4603
+ itemHeight,
4604
+ items,
4605
+ uid,
4606
+ x,
4607
+ y
4608
+ } = state;
4609
+ const actualIndex = index;
4610
+ if (actualIndex < 0 || actualIndex >= items.length) {
4611
+ return state;
4612
+ }
4613
+
4614
+ // Calculate the position for the context menu
4615
+ // The settings button is at the bottom right of the extension list item
4616
+ const itemY = y + headerHeight + actualIndex * itemHeight - deltaY;
4617
+ const menuX = x + 200; // Position near the right side of the extension item
4618
+ const menuY = itemY + itemHeight - 10; // Position at the bottom of the extension item
4619
+
4620
+ await show2(uid, ManageExtension, menuX, menuY, {
4621
+ builtin: items[actualIndex].builtin === true,
4622
+ disabled: items[actualIndex].disabled === true,
4623
+ menuId: ManageExtension,
4624
+ status: items[actualIndex].status
4625
+ });
4626
+ return state;
4674
4627
  };
4675
4628
 
4676
- const getExtensionListItemId = focused => {
4677
- if (focused) {
4678
- return `ExtensionActive`;
4629
+ const handleUninstall = async (state, id) => {
4630
+ try {
4631
+ await uninstall$2(id);
4632
+ return setExtensionStatus(state, id, NotInstalled);
4633
+ } catch (error) {
4634
+ await showErrorDialog(error);
4635
+ return state;
4679
4636
  }
4680
- return undefined;
4681
4637
  };
4682
4638
 
4683
- const extensionListItemMetadataNode = {
4684
- childCount: 2,
4685
- className: ExtensionListItemMetadata,
4686
- type: Div
4687
- };
4688
- const getStatisticVirtualDom = (label, value, className) => {
4689
- const accessibleLabel = `${label}: ${value}`;
4690
- return [{
4691
- ariaLabel: accessibleLabel,
4692
- childCount: 1,
4693
- className: mergeClassNames(ExtensionListItemStatistic, className),
4694
- title: accessibleLabel,
4695
- type: Span
4696
- }, text(value)];
4697
- };
4698
- const getExtensionStatisticsVirtualDom = (downloadCount, rating$1) => {
4699
- return [extensionListItemMetadataNode, ...getStatisticVirtualDom(downloads(), downloadCount, ExtensionListItemDownloadCount), ...getStatisticVirtualDom(rating(), rating$1, ExtensionListItemRating)];
4639
+ const handleWheel = (state, deltaMode, deltaY) => {
4640
+ number(deltaMode);
4641
+ number(deltaY);
4642
+ return setDeltaY(state, state.deltaY + deltaY * state.scrollSensitivity);
4700
4643
  };
4701
4644
 
4702
- const getExtensionListItemStatisticsVirtualDom = (hasStatistics, downloadCount, rating) => {
4703
- if (!hasStatistics) {
4704
- return [];
4645
+ const createExtensionManagementWorkerRpc = async () => {
4646
+ try {
4647
+ const rpc = await create$6({
4648
+ commandMap: {},
4649
+ send: port => sendMessagePortToExtensionManagementWorker(port, 0)
4650
+ });
4651
+ return rpc;
4652
+ } catch (error) {
4653
+ throw new VError(error, `Failed to create extension management rpc`);
4705
4654
  }
4706
- return getExtensionStatisticsVirtualDom(downloadCount, rating);
4707
4655
  };
4708
4656
 
4709
- const getListItemDetail = linked => {
4710
- return {
4711
- childCount: linked ? 4 : 3,
4712
- className: ExtensionListItemDetail,
4713
- type: Div
4714
- };
4715
- };
4716
- const listItemName = {
4717
- childCount: 1,
4718
- className: ExtensionListItemName,
4719
- type: Div
4720
- };
4721
- const listItemDescription = {
4722
- childCount: 1,
4723
- className: ExtensionListItemDescription,
4724
- type: Div
4725
- };
4726
- const listItemAuthorName = {
4727
- childCount: 1,
4728
- className: ExtensionListItemAuthorName,
4729
- type: Div
4730
- };
4731
- const getLinkedIconVirtualDom = linked$1 => {
4732
- if (!linked$1) {
4733
- return [];
4657
+ const initializeExtensionManagementWorker = async () => {
4658
+ try {
4659
+ const rpc = await createExtensionManagementWorkerRpc();
4660
+ set$4(rpc);
4661
+ } catch {
4662
+ // ignore
4734
4663
  }
4735
- const label = linked();
4736
- return [{
4737
- ariaLabel: label,
4738
- childCount: 0,
4739
- className: mergeClassNames(MaskIcon, 'MaskIconLinkExternal', ExtensionListItemLinkedIcon),
4740
- role: Image,
4741
- title: 'Extension is linked',
4742
- type: Div
4743
- }];
4744
4664
  };
4745
- const getExtensionListItemVirtualDom = extension => {
4746
- const {
4747
- builtin = false,
4748
- description,
4749
- disabled = false,
4750
- downloadCount = 'n/a',
4751
- focused,
4752
- icon,
4753
- id,
4754
- linked = false,
4755
- name,
4756
- posInSet,
4757
- publisher,
4758
- rating = 'n/a',
4759
- setSize,
4760
- status
4761
- } = extension;
4762
- const actionsDom = getExtensionActionsVirtualDom(id, builtin, disabled, status);
4763
- const hasStatistics = !builtin;
4764
- const dom = [{
4765
- ariaPosInSet: posInSet,
4766
- ariaRoleDescription: Extension,
4767
- ariaSetSize: setSize,
4768
- childCount: 2,
4769
- className: getExtensionListItemClassName(focused, disabled),
4770
- id: getExtensionListItemId(focused),
4771
- role: ListItem,
4772
- type: Div
4773
- }, {
4774
- childCount: 0,
4775
- className: ExtensionListItemIcon,
4776
- role: None,
4777
- src: icon,
4778
- type: Img
4779
- }, getListItemDetail(linked), listItemName, text(name), listItemDescription, text(description), getExtensionListItemFooter(hasStatistics), listItemAuthorName, text(publisher), ...getExtensionListItemStatisticsVirtualDom(hasStatistics, downloadCount, rating), ...actionsDom, ...getLinkedIconVirtualDom(linked)];
4780
- return dom;
4665
+
4666
+ const initialize = async () => {
4667
+ await initializeExtensionManagementWorker();
4781
4668
  };
4782
4669
 
4783
- const getListClassName = focusOutline => {
4784
- const className = focusOutline ? mergeClassNames(ListItems, FocusOutline) : ListItems;
4785
- return className;
4670
+ const installAnotherVersion = async state => {
4671
+ await invoke$4('ConfirmPrompt.prompt', 'not implemented', undefined);
4672
+ return state;
4786
4673
  };
4787
- const getExtensionsListVirtualDom = (visibleExtensions, focusOutline) => {
4788
- const dom = [{
4789
- ariaLabel: extensions(),
4790
- childCount: visibleExtensions.length,
4791
- className: getListClassName(focusOutline),
4792
- onBlur: HandleBlur,
4793
- onContextmenu: HandleContextMenu,
4794
- onContextMenu: HandleContextMenu,
4795
- onFocus: HandleFocus,
4796
- onPointerDown: HandlePointerDown,
4797
- onTouchEnd: HandleTouchEnd,
4798
- onTouchMove: HandleTouchMove,
4799
- onTouchStart: HandleTouchStart,
4800
- onWheel: HandleWheel,
4801
- role: List$1,
4802
- tabIndex: Focusable,
4803
- type: Div
4804
- }, ...visibleExtensions.flatMap(getExtensionListItemVirtualDom)];
4805
- return dom;
4674
+
4675
+ const Small = 1;
4676
+ const Normal = 2;
4677
+ const Large = 3;
4678
+
4679
+ const getViewletSize = width => {
4680
+ if (width < 180) {
4681
+ return Small;
4682
+ }
4683
+ if (width < 768) {
4684
+ return Normal;
4685
+ }
4686
+ return Large;
4806
4687
  };
4807
4688
 
4808
- const getExtensionsVirtualDom = (visibleExtensions, focusOutline) => {
4809
- const dom = getExtensionsListVirtualDom(visibleExtensions, focusOutline);
4810
- // TODO
4811
- return dom;
4689
+ const getIsFirefox = () => {
4690
+ const globalWithNavigator = globalThis;
4691
+ return globalWithNavigator.navigator?.userAgent.toLowerCase().includes('firefox') ?? false;
4812
4692
  };
4813
4693
 
4814
- const noExtensionsFoundNode = {
4815
- childCount: 1,
4816
- className: NoExtensionsFoundMessage,
4817
- type: Div
4694
+ const getSavedValue = savedState => {
4695
+ if (savedState && typeof savedState === 'object' && 'searchValue' in savedState && typeof savedState.searchValue === 'string') {
4696
+ return savedState.searchValue;
4697
+ }
4698
+ return '';
4818
4699
  };
4819
- const getNoExtensionsFoundVirtualDom = message => {
4820
- return [noExtensionsFoundNode, text(message)];
4700
+ const getSavedDeltaY = savedState => {
4701
+ if (savedState && typeof savedState === 'object' && 'deltaY' in savedState && typeof savedState.deltaY === 'number' && !Number.isNaN(savedState.deltaY)) {
4702
+ return savedState.deltaY;
4703
+ }
4704
+ return 0;
4821
4705
  };
4822
4706
 
4823
- const scrollBarNode = {
4824
- childCount: 1,
4825
- className: mergeClassNames(ScrollBar, ScrollBarSmall),
4826
- onPointerDown: HandleScrollBarPointerDown,
4827
- // TODO support pointercapture event
4828
- type: Div
4829
- };
4830
- const scrollBarThumbNode = {
4831
- childCount: 0,
4832
- className: ScrollBarThumb,
4833
- type: Div
4707
+ const restoreState = savedState => {
4708
+ const searchValue = getSavedValue(savedState);
4709
+ const savedDeltaY = getSavedDeltaY(savedState);
4710
+ return {
4711
+ deltaY: savedDeltaY,
4712
+ searchValue
4713
+ };
4834
4714
  };
4835
- const getScrollBarVirtualDom = (scrollBarHeight, scrollBarTop) => {
4836
- const shouldShowScrollbar = scrollBarHeight > 0;
4837
- if (!shouldShowScrollbar) {
4838
- return [];
4715
+
4716
+ const loadContentWithContext = async (context, savedState) => {
4717
+ const {
4718
+ uid
4719
+ } = context.getState();
4720
+ const loadToken = getToken(uid);
4721
+ try {
4722
+ const initialState = context.getState();
4723
+ const {
4724
+ assetDir,
4725
+ platform,
4726
+ width
4727
+ } = initialState;
4728
+ const {
4729
+ deltaY,
4730
+ searchValue: restoredSearchValue
4731
+ } = restoreState(savedState);
4732
+ const size = getViewletSize(width);
4733
+ const scrollSensitivity = getIsFirefox() ? 2.5 : 1;
4734
+ await context.updateState(state => {
4735
+ if (!state.initial) {
4736
+ return state;
4737
+ }
4738
+ return {
4739
+ ...state,
4740
+ deltaY,
4741
+ initial: false,
4742
+ inputSource: Script,
4743
+ scrollSensitivity,
4744
+ searchValue: restoredSearchValue,
4745
+ size
4746
+ };
4747
+ });
4748
+ const allExtensions = await getAllExtensions(assetDir, platform);
4749
+ const normalized = normalizeExtensions(allExtensions, platform, assetDir);
4750
+ await context.updateState(state => ({
4751
+ ...state,
4752
+ allExtensions: normalized
4753
+ }));
4754
+ await handleChangeWithContext(context, {}, false);
4755
+ } finally {
4756
+ finish(uid, loadToken);
4839
4757
  }
4840
- return [scrollBarNode, scrollBarThumbNode];
4841
4758
  };
4842
4759
 
4843
- const getVisibleItem = (item, setSize, itemHeight, minLineY, relative, i, focusedIndex) => {
4844
- // TODO use normal parameters
4845
- const {
4846
- builtin,
4847
- description,
4848
- disabled,
4849
- downloadCount,
4850
- icon,
4851
- id,
4852
- linked,
4853
- name,
4854
- publisher,
4855
- rating,
4856
- status
4857
- } = item;
4760
+ const openSuggest = state => {
4761
+ const completionItems = getCompletionItems(state.searchValue, state.cursorOffset);
4762
+ if (completionItems.length === 0) {
4763
+ return state;
4764
+ }
4858
4765
  return {
4859
- builtin,
4860
- description,
4861
- disabled,
4862
- downloadCount,
4863
- focused: i === focusedIndex,
4864
- icon,
4865
- id,
4866
- index: i,
4867
- linked,
4868
- name,
4869
- posInSet: i + 1,
4870
- publisher,
4871
- rating,
4872
- setSize,
4873
- status,
4874
- top: (i - minLineY) * itemHeight - relative
4766
+ ...state,
4767
+ completionFocusedIndex: 0,
4768
+ completionItems,
4769
+ suggestOpen: true
4875
4770
  };
4876
4771
  };
4877
4772
 
4878
- const getVisible = state => {
4773
+ const getCss = state => {
4879
4774
  const {
4775
+ cursorOffset,
4880
4776
  deltaY,
4881
- focusedIndex,
4777
+ headerHeight,
4882
4778
  itemHeight,
4883
- items,
4884
- maxLineY,
4885
- minLineY
4779
+ scrollBarHeight,
4780
+ scrollBarY,
4781
+ width
4886
4782
  } = state;
4887
- const setSize = items.length;
4888
- const visible = [];
4889
- const relative = deltaY % itemHeight;
4890
- for (let i = minLineY; i < maxLineY; i++) {
4891
- const item = items[i];
4892
- visible.push(getVisibleItem(item, setSize, itemHeight, minLineY, relative, i, focusedIndex));
4893
- }
4894
- return visible;
4895
- };
4783
+ const relative = -(deltaY % itemHeight);
4784
+ const roundedScrollBarY = Math.round(scrollBarY);
4785
+ const maximumCompletionLeft = Math.max(8, width - 168);
4786
+ const completionLeft = Math.min(Math.round(8 + cursorOffset * 7.5), maximumCompletionLeft);
4787
+ const completionTop = Math.max(0, headerHeight - 10);
4788
+ return `.Extensions .ScrollBarThumb {
4789
+ height: ${scrollBarHeight}px;
4790
+ translate: 0 ${roundedScrollBarY}px;
4791
+ }
4896
4792
 
4897
- const contentNode = {
4898
- childCount: 2,
4899
- className: mergeClassNames(Viewlet, List),
4900
- type: Div
4793
+
4794
+ /* TODO: avoid using negative margin. find a better way*/
4795
+ .ExtensionListItem:nth-child(1) {
4796
+ margin-top: ${relative}px;
4797
+ }
4798
+
4799
+ .ExtensionListItem {
4800
+ box-sizing: border-box;
4801
+ position: relative !important;
4802
+ flex-shrink: 0;
4803
+ }
4804
+
4805
+ .ExtensionListItemLinkedIcon {
4806
+ position: absolute;
4807
+ top: 6px;
4808
+ right: 6px;
4809
+ width: 14px;
4810
+ height: 14px;
4811
+ color: var(--WorkbenchForeground, rgb(188, 190, 190));
4812
+ opacity: 0.8;
4813
+ }
4814
+
4815
+ .ExtensionListItemDisabled:not(.ExtensionActive) {
4816
+ background: color-mix(in srgb, var(--SideBarBackground, rgb(30, 35, 36)) 95%, black);
4817
+ color: var(--ExtensionDisabledForeground, color-mix(in srgb, var(--WorkbenchForeground) 70%, black));
4818
+ }
4819
+
4820
+ .Extensions .ListItems {
4821
+ display: flex;
4822
+ flex-direction: column;
4823
+ gap: 0;
4824
+ overflow-y: hidden;
4825
+ }
4826
+
4827
+ .ExtensionHeader {
4828
+ contain: layout style;
4829
+ position: relative;
4830
+ z-index: 1;
4831
+ }
4832
+
4833
+ .ExtensionListItemFooter {
4834
+ justify-content: flex-end;
4835
+ padding-right: 2px;
4836
+ }
4837
+
4838
+ .ExtensionListItemAuthorName {
4839
+ flex: 1;
4840
+ }
4841
+
4842
+ .ExtensionActions {
4843
+ display: flex;
4844
+ gap: 6px;
4845
+ }
4846
+
4847
+ .ExtensionActionButton {
4848
+ padding: 0 5px;
4849
+ }
4850
+
4851
+ .ExtensionSearchCompletionWidget {
4852
+ position: absolute;
4853
+ left: ${completionLeft}px;
4854
+ top: ${completionTop}px;
4855
+ width: min(320px, calc(100% - ${completionLeft + 8}px));
4856
+ max-height: 240px;
4857
+ overflow-y: auto;
4858
+ z-index: 10;
4859
+ box-sizing: border-box;
4860
+ border: 1px solid var(--CompletionListBorder, #95a29d);
4861
+ background: var(--CompletionListBackground, #282e2f);
4862
+ color: var(--CompletionListForeground, white);
4863
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.36);
4864
+ font-size: 13px;
4865
+ line-height: 20px;
4866
+ user-select: none;
4867
+ }
4868
+
4869
+ .ExtensionSearchCompletionItem {
4870
+ display: block;
4871
+ width: 100%;
4872
+ min-height: 20px;
4873
+ padding: 0 6px;
4874
+ border: 0;
4875
+ background: transparent;
4876
+ color: inherit;
4877
+ font: inherit;
4878
+ line-height: inherit;
4879
+ text-align: left;
4880
+ overflow: hidden;
4881
+ text-overflow: ellipsis;
4882
+ white-space: nowrap;
4883
+ cursor: pointer;
4884
+ }
4885
+
4886
+ .ExtensionSearchCompletionItem:hover {
4887
+ background: var(--CompletionListItemHoverBackground, rgba(64, 92, 80, 0.2));
4888
+ }
4889
+
4890
+ .ExtensionSearchCompletionItemFocused {
4891
+ background: var(--CompletionListItemActiveBackground, #405c50);
4892
+ color: var(--CompletionListItemActiveForeground);
4893
+ }
4894
+
4895
+ .ExtensionSearchCompletionHighlight {
4896
+ color: var(--CompletionHighlightForeground, #e1b974);
4897
+ font-weight: 700;
4898
+ }
4899
+ `;
4901
4900
  };
4902
- const extensionsNode = {
4903
- ariaBusy: false,
4904
- ariaLive: 'polite',
4905
- childCount: 2,
4906
- className: mergeClassNames(Viewlet, Extensions),
4907
- role: None$3,
4908
- type: Div
4901
+
4902
+ const renderCss = newState => {
4903
+ const {
4904
+ uid
4905
+ } = newState;
4906
+ const css = getCss(newState);
4907
+ return [SetCss, uid, css];
4909
4908
  };
4910
- const getContentVirtualDom = (visibleExtensions, message, scrollBarHeight, scrollBarY, focusOutline) => {
4911
- if (message) {
4912
- return getNoExtensionsFoundVirtualDom(message);
4909
+
4910
+ const getSelector = focus => {
4911
+ switch (focus) {
4912
+ case Input:
4913
+ return `[name="${Extensions}"]`;
4914
+ case List$2:
4915
+ return '.ListItems';
4916
+ default:
4917
+ return '';
4913
4918
  }
4914
- return [contentNode, ...getExtensionsVirtualDom(visibleExtensions, focusOutline), ...getScrollBarVirtualDom(scrollBarHeight)];
4915
4919
  };
4916
- const getExtensionsViewVirtualDom = state => {
4917
- const visibleExtensions = getVisible(state);
4920
+ const renderFocus = newState => {
4918
4921
  const {
4919
- completionFocusedIndex,
4920
- completionItems,
4921
4922
  focus,
4922
- focusedIndex,
4923
- inputActions,
4924
- message,
4925
- placeholder,
4926
- scrollBarHeight,
4927
- scrollBarY,
4928
- suggestOpen
4929
- } = state;
4930
- const focusOutline = focusedIndex === -1 && focus === List$2;
4931
- return [extensionsNode, ...getExtensionHeaderVirtualDom(placeholder, inputActions, completionItems, completionFocusedIndex, suggestOpen), ...getContentVirtualDom(visibleExtensions, message, scrollBarHeight, scrollBarY, focusOutline)];
4923
+ uid
4924
+ } = newState;
4925
+ if (!focus) {
4926
+ return [];
4927
+ }
4928
+ const selector = getSelector(focus);
4929
+ return [FocusSelector, uid, selector];
4932
4930
  };
4933
4931
 
4934
- const renderItems2 = newState => {
4932
+ const renderFocusContext = newState => {
4935
4933
  const {
4936
- initial,
4937
4934
  uid
4938
4935
  } = newState;
4939
- if (initial) {
4940
- return [SetDom2, uid, []];
4936
+ if (newState.focus === Input) {
4937
+ return ['Viewlet.setFocusContext', uid, FocusExtensionsInput];
4941
4938
  }
4942
- const dom = getExtensionsViewVirtualDom(newState);
4943
- return [SetDom2, uid, dom];
4939
+ if (newState.focus === List$2) {
4940
+ return ['Viewlet.setFocusContext', uid, FocusExtensions];
4941
+ }
4942
+ return [];
4943
+ };
4944
+
4945
+ const renderHeader = newState => {
4946
+ const actions = getInputActions(newState.searchValue.length > 0);
4947
+ const dom = getExtensionHeaderVirtualDom(newState.placeholder, actions, newState.completionItems, newState.completionFocusedIndex, newState.suggestOpen);
4948
+ return ['setHeaderDom', dom];
4944
4949
  };
4945
4950
 
4946
4951
  // TODO cache rendered dom so that it can be used for dom diffing
@@ -4980,7 +4985,7 @@ const renderScrollBar = newState => {
4980
4985
  };
4981
4986
 
4982
4987
  const renderSearchValue = newState => {
4983
- return [/* method */'Viewlet.setValueByName', newState.uid, Extensions$1, newState.searchValue];
4988
+ return [/* method */'Viewlet.setValueByName', newState.uid, Extensions, newState.searchValue];
4984
4989
  };
4985
4990
 
4986
4991
  const titleFilters = [{
@@ -5102,7 +5107,7 @@ const getIconVirtualDom = (icon, type = Div) => {
5102
5107
  return {
5103
5108
  childCount: 0,
5104
5109
  className: mergeClassNames(MaskIcon, `MaskIcon${icon}`),
5105
- role: None,
5110
+ role: None$1,
5106
5111
  type
5107
5112
  };
5108
5113
  };
@@ -5353,6 +5358,7 @@ const commandMap = {
5353
5358
  'SearchExtensions.focusPreviousPage': wrapCommand(focusPreviousPage),
5354
5359
  'SearchExtensions.getActions': getActions,
5355
5360
  'SearchExtensions.getCommandIds': getCommandIds,
5361
+ 'SearchExtensions.getComponentDom': getComponentDom,
5356
5362
  'SearchExtensions.getComponentState': getComponentState,
5357
5363
  'SearchExtensions.getKeyBindings': getKeyBindings,
5358
5364
  'SearchExtensions.getMenuEntries': getMenuEntriesList,