@lvce-editor/extension-detail-view 7.11.0 → 7.14.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.
@@ -1,13 +1,13 @@
1
1
  const emptyObject = {};
2
- const RE_PLACEHOLDER = /\{(PH\d+)\}/g;
3
2
  const i18nString = (key, placeholders = emptyObject) => {
4
3
  if (placeholders === emptyObject) {
5
4
  return key;
6
5
  }
7
- const replacer = (match, rest) => {
8
- return placeholders[rest];
9
- };
10
- return key.replaceAll(RE_PLACEHOLDER, replacer);
6
+ let result = key;
7
+ for (const [placeholder, replacement] of Object.entries(placeholders)) {
8
+ result = result.split(`{${placeholder}}`).join(String(replacement));
9
+ }
10
+ return result;
11
11
  };
12
12
 
13
13
  const ActivationEvents$1 = 'Activation Events';
@@ -23,9 +23,11 @@ const CopyLink = 'Copy Link';
23
23
  const Created = 'Created';
24
24
  const Details$1 = 'Details';
25
25
  const Disable$1 = 'Disable';
26
+ const DownloadCount = 'Downloads';
26
27
  const Elements = 'Elements';
27
28
  const EmptyCommandsArray = 'The extension contributes an empty commands array.';
28
29
  const Enable$1 = 'Enable';
30
+ const ExtensionNotAvailable = 'The extension "{PH1}" is not available in this version of LVCE Editor.';
29
31
  const Features$2 = 'Features';
30
32
  const FileExtensions = 'File Extensions';
31
33
  const FileMatch = 'File Match';
@@ -35,9 +37,10 @@ const Identifier = 'Identifier';
35
37
  const ImportTime = 'Import Time: ';
36
38
  const Installation = 'Installation';
37
39
  const InvalidLink = 'Invalid link';
40
+ const ImageCannotBeLoaded = 'Image cannot be loaded';
38
41
  const Issues = 'Issues';
39
42
  const JsonValidation$1 = 'Json Validation';
40
- const Label = 'Label';
43
+ const Label$1 = 'Label';
41
44
  const LastReleased = 'Last Released';
42
45
  const LastUpdated = 'Last Updated';
43
46
  const License = 'License';
@@ -50,6 +53,7 @@ const OpenInNewTab = 'Open in New Tab';
50
53
  const ProgrammingLanguages$1 = 'Programming Languages';
51
54
  const PropertyMustBeOfTypeString = 'Property must be a string';
52
55
  const Published = 'Published';
56
+ const Rating = 'Rating';
53
57
  const Repository = 'Repository';
54
58
  const Resources$1 = 'Resources';
55
59
  const RuntimeStatus$1 = 'Runtime Status';
@@ -67,6 +71,8 @@ const Status = 'Status';
67
71
  const StringMustNotBeEmpty = 'String must not be empty';
68
72
  const Theme$1 = 'Theme';
69
73
  const Uninstall$1 = 'Uninstall';
74
+ const UnableToLoadExtension = 'Unable to load extension';
75
+ const UnableToLoadExtensionWithError = 'The extension details could not be loaded: {PH1}';
70
76
  const UnsupportedFeature = 'Unsupported Feature';
71
77
  const Version = 'Version';
72
78
  const WebViews$1 = 'WebViews';
@@ -114,7 +120,7 @@ const openInNewTab = () => {
114
120
  return i18nString(OpenInNewTab);
115
121
  };
116
122
  const label = () => {
117
- return i18nString(Label);
123
+ return i18nString(Label$1);
118
124
  };
119
125
  const openImageInNewTab$1 = () => {
120
126
  return i18nString(OpenImageInNewTab);
@@ -137,6 +143,11 @@ const setColorTheme$2 = () => {
137
143
  const enable = () => {
138
144
  return i18nString(Enable$1);
139
145
  };
146
+ const extensionNotAvailable = extensionId => {
147
+ return i18nString(ExtensionNotAvailable, {
148
+ PH1: extensionId
149
+ });
150
+ };
140
151
  const theme = () => {
141
152
  return i18nString(Theme$1);
142
153
  };
@@ -167,6 +178,9 @@ const id$1 = () => {
167
178
  const name = () => {
168
179
  return i18nString(Name);
169
180
  };
181
+ const imageCannotBeLoaded = () => {
182
+ return i18nString(ImageCannotBeLoaded);
183
+ };
170
184
  const fileExtensions = () => {
171
185
  return i18nString(FileExtensions);
172
186
  };
@@ -188,6 +202,14 @@ const elements = () => {
188
202
  const uninstall = () => {
189
203
  return i18nString(Uninstall$1);
190
204
  };
205
+ const unableToLoadExtension = () => {
206
+ return i18nString(UnableToLoadExtension);
207
+ };
208
+ const unableToLoadExtensionWithError = errorMessage => {
209
+ return i18nString(UnableToLoadExtensionWithError, {
210
+ PH1: errorMessage
211
+ });
212
+ };
191
213
  const scrollToTop = () => {
192
214
  return i18nString(ScrollToTop$1);
193
215
  };
@@ -239,6 +261,12 @@ const repository = () => {
239
261
  const license = () => {
240
262
  return i18nString(License);
241
263
  };
264
+ const downloadCount = () => {
265
+ return i18nString(DownloadCount);
266
+ };
267
+ const rating = () => {
268
+ return i18nString(Rating);
269
+ };
242
270
  const propertyMustBeOfTypeString = () => {
243
271
  return i18nString(PropertyMustBeOfTypeString);
244
272
  };
@@ -302,9 +330,14 @@ const Panel = 'panel';
302
330
  const Tab = 'tab';
303
331
  const TabList = 'tablist';
304
332
 
333
+ const Audio = 0;
305
334
  const Button$1 = 1;
335
+ const Col = 2;
336
+ const ColGroup = 3;
306
337
  const Div = 4;
307
338
  const H1 = 5;
339
+ const Input = 6;
340
+ const Kbd = 7;
308
341
  const Span = 8;
309
342
  const Table$1 = 9;
310
343
  const TBody = 10;
@@ -313,20 +346,159 @@ const Text$1 = 12;
313
346
  const Th = 13;
314
347
  const THead = 14;
315
348
  const Tr = 15;
349
+ const I = 16;
316
350
  const Img = 17;
351
+ const Root = 0;
352
+ const Ins = 20;
353
+ const Del = 21;
317
354
  const H2 = 22;
355
+ const H3 = 23;
356
+ const H4 = 24;
357
+ const H5 = 25;
358
+ const H6 = 26;
359
+ const Article = 27;
318
360
  const Aside$1 = 28;
361
+ const Footer = 29;
362
+ const Header = 30;
363
+ const Nav = 40;
364
+ const Section = 41;
365
+ const Search = 42;
319
366
  const Dd = 43;
320
367
  const Dl = 44;
368
+ const Figcaption = 45;
369
+ const Figure = 46;
370
+ const Hr = 47;
321
371
  const Li = 48;
372
+ const Ol = 49;
322
373
  const P = 50;
323
374
  const Pre = 51;
324
375
  const A = 53;
376
+ const Abbr = 54;
377
+ const Br = 55;
378
+ const Cite = 56;
379
+ const Data = 57;
380
+ const Time = 58;
381
+ const Tfoot = 59;
325
382
  const Ul = 60;
383
+ const Video = 61;
384
+ const TextArea = 62;
385
+ const Select = 63;
386
+ const Option = 64;
326
387
  const Code$2 = 65;
388
+ const Label = 66;
327
389
  const Dt = 67;
390
+ const Iframe = 68;
391
+ const Main = 69;
392
+ const Strong = 70;
393
+ const Em = 71;
394
+ const Style = 72;
395
+ const Html = 73;
396
+ const Head = 74;
397
+ const Title = 75;
398
+ const Meta = 76;
399
+ const Canvas = 77;
400
+ const Form = 78;
401
+ const BlockQuote = 79;
402
+ const Quote = 80;
403
+ const Circle = 81;
404
+ const Defs = 82;
405
+ const Ellipse = 83;
406
+ const G = 84;
407
+ const Line = 85;
408
+ const Path = 86;
409
+ const Polygon = 87;
410
+ const Polyline = 88;
411
+ const Rect = 89;
412
+ const Svg = 90;
413
+ const Use = 91;
328
414
  const Reference = 100;
329
415
 
416
+ const VirtualDomElements = {
417
+ __proto__: null,
418
+ A,
419
+ Abbr,
420
+ Article,
421
+ Aside: Aside$1,
422
+ Audio,
423
+ BlockQuote,
424
+ Br,
425
+ Button: Button$1,
426
+ Canvas,
427
+ Circle,
428
+ Cite,
429
+ Code: Code$2,
430
+ Col,
431
+ ColGroup,
432
+ Data,
433
+ Dd,
434
+ Defs,
435
+ Del,
436
+ Div,
437
+ Dl,
438
+ Dt,
439
+ Ellipse,
440
+ Em,
441
+ Figcaption,
442
+ Figure,
443
+ Footer,
444
+ Form,
445
+ G,
446
+ H1,
447
+ H2,
448
+ H3,
449
+ H4,
450
+ H5,
451
+ H6,
452
+ Head,
453
+ Header,
454
+ Hr,
455
+ Html,
456
+ I,
457
+ Iframe,
458
+ Img,
459
+ Input,
460
+ Ins,
461
+ Kbd,
462
+ Label,
463
+ Li,
464
+ Line,
465
+ Main,
466
+ Meta,
467
+ Nav,
468
+ Ol,
469
+ Option,
470
+ P,
471
+ Path,
472
+ Polygon,
473
+ Polyline,
474
+ Pre,
475
+ Quote,
476
+ Rect,
477
+ Reference,
478
+ Root,
479
+ Search,
480
+ Section,
481
+ Select,
482
+ Span,
483
+ Strong,
484
+ Style,
485
+ Svg,
486
+ TBody,
487
+ THead,
488
+ Table: Table$1,
489
+ Td,
490
+ Text: Text$1,
491
+ TextArea,
492
+ Tfoot,
493
+ Th,
494
+ Time,
495
+ Title,
496
+ Tr,
497
+ Ul,
498
+ Use,
499
+ Video
500
+ };
501
+
330
502
  const ClientX = 'event.clientX';
331
503
  const ClientY = 'event.clientY';
332
504
  const TargetHref = 'event.target.href';
@@ -371,6 +543,8 @@ const text = data => {
371
543
  };
372
544
  };
373
545
 
546
+ new Set(Object.values(VirtualDomElements));
547
+
374
548
  const SetText = 1;
375
549
  const Replace = 2;
376
550
  const SetAttribute = 3;
@@ -642,6 +816,7 @@ const Categories = 'Categories';
642
816
  const Category = 'Category';
643
817
  const Changelog$1 = 'Changelog';
644
818
  const MaskIcon = 'MaskIcon';
819
+ const MaskIconWarning = 'MaskIconWarning';
645
820
  const ResourceIcon = 'ResourceIcon';
646
821
  const Code$1 = 'Code';
647
822
  const DefaultMarkdown = 'DefaultMarkdown';
@@ -651,13 +826,22 @@ const DefinitionListItemHeading = 'DefinitionListItemHeading';
651
826
  const DefinitionListItemValue = 'DefinitionListItemValue';
652
827
  const ExtensionDetail = 'ExtensionDetail';
653
828
  const ExtensionDetailDescription = 'ExtensionDetailDescription';
829
+ const ExtensionDetailDownloadCount = 'ExtensionDetailDownloadCount';
830
+ const ExtensionDetailError = 'ExtensionDetailError';
831
+ const ExtensionDetailErrorCard = 'ExtensionDetailErrorCard';
832
+ const ExtensionDetailErrorIcon = 'ExtensionDetailErrorIcon';
833
+ const ExtensionDetailErrorMessage = 'ExtensionDetailErrorMessage';
834
+ const ExtensionDetailErrorTitle = 'ExtensionDetailErrorTitle';
654
835
  const ExtensionDetailHeader = 'ExtensionDetailHeader';
655
836
  const ExtensionDetailHeaderActions = 'ExtensionDetailHeaderActions';
656
837
  const ExtensionDetailHeaderDetails = 'ExtensionDetailHeaderDetails';
657
838
  const ExtensionDetailIcon = 'ExtensionDetailIcon';
839
+ const ExtensionDetailMetadata = 'ExtensionDetailMetadata';
658
840
  const ExtensionDetailName = 'ExtensionDetailName';
659
841
  const ExtensionDetailNameBadge = 'ExtensionDetailNameBadge';
660
842
  const ExtensionDetailPanel = 'ExtensionDetailPanel';
843
+ const ExtensionDetailRating = 'ExtensionDetailRating';
844
+ const ExtensionDetailStatistic = 'ExtensionDetailStatistic';
661
845
  const ExtensionDetailTab = 'ExtensionDetailTab';
662
846
  const ExtensionDetailTabs = 'ExtensionDetailTabs';
663
847
  const ExtensionDetailTabSelected = 'ExtensionDetailTabSelected';
@@ -669,6 +853,7 @@ const FeatureWebView = 'FeatureWebView';
669
853
  const Large$1 = 'Large';
670
854
  const Link$1 = 'Link';
671
855
  const Markdown = 'Markdown';
856
+ const MarkdownImageError = 'MarkdownImageError';
672
857
  const MoreInfo = 'MoreInfo';
673
858
  const MoreInfoEntry = 'MoreInfoEntry';
674
859
  const MoreInfoEntryKey = 'MoreInfoEntryKey';
@@ -1375,7 +1560,7 @@ class AssertionError extends Error {
1375
1560
  const Object$1 = 1;
1376
1561
  const Number$1 = 2;
1377
1562
  const Array$1 = 3;
1378
- const String = 4;
1563
+ const String$1 = 4;
1379
1564
  const Boolean$1 = 5;
1380
1565
  const Function = 6;
1381
1566
  const Null = 7;
@@ -1387,7 +1572,7 @@ const getType = value => {
1387
1572
  case 'function':
1388
1573
  return Function;
1389
1574
  case 'string':
1390
- return String;
1575
+ return String$1;
1391
1576
  case 'object':
1392
1577
  if (value === null) {
1393
1578
  return Null;
@@ -1410,7 +1595,7 @@ const number = value => {
1410
1595
  };
1411
1596
  const string = value => {
1412
1597
  const type = getType(value);
1413
- if (type !== String) {
1598
+ if (type !== String$1) {
1414
1599
  throw new AssertionError('expected value to be of type string');
1415
1600
  }
1416
1601
  };
@@ -1457,7 +1642,6 @@ const walkValue = (value, transferrables, isTransferrable) => {
1457
1642
  for (const property of Object.values(value)) {
1458
1643
  walkValue(property, transferrables, isTransferrable);
1459
1644
  }
1460
- return;
1461
1645
  }
1462
1646
  };
1463
1647
  const getTransferrables = value => {
@@ -1611,7 +1795,14 @@ class IpcError extends VError {
1611
1795
  const cause = new Error(message);
1612
1796
  // @ts-ignore
1613
1797
  cause.code = code;
1614
- cause.stack = stack;
1798
+ if (stack) {
1799
+ Object.defineProperty(cause, 'stack', {
1800
+ configurable: true,
1801
+ enumerable: false,
1802
+ value: stack,
1803
+ writable: true
1804
+ });
1805
+ }
1615
1806
  super(cause, betterMessage);
1616
1807
  } else {
1617
1808
  super(betterMessage);
@@ -2810,6 +3001,25 @@ const HandleTabFocus = 18;
2810
3001
  const HandleResourceLinkClick = 19;
2811
3002
  const HandleMouseEnterEnable = 20;
2812
3003
  const HandleMouseLeaveEnable = 21;
3004
+ const HandleHeaderContextMenu = 22;
3005
+ const HandleMarkdownImageError = 23;
3006
+
3007
+ const addMarkdownImageErrorHandler = node => {
3008
+ if (node.type !== Img) {
3009
+ return node;
3010
+ }
3011
+ const {
3012
+ onError,
3013
+ ...rest
3014
+ } = node;
3015
+ return {
3016
+ onError: HandleMarkdownImageError,
3017
+ ...rest
3018
+ };
3019
+ };
3020
+ const addMarkdownImageErrorHandlers = dom => {
3021
+ return dom.map(addMarkdownImageErrorHandler);
3022
+ };
2813
3023
 
2814
3024
  const ActivationEvents = 'ActivationEvents';
2815
3025
  const Changelog = 'Changelog';
@@ -2851,19 +3061,22 @@ const {
2851
3061
  set: set$3
2852
3062
  } = MarkdownWorker;
2853
3063
 
3064
+ const addScrollToTopVirtualDom = dom => {
3065
+ const [firstNode, ...rest] = dom;
3066
+ const extraDom = getScrollToTopVirtualDom();
3067
+ return [{
3068
+ ...firstNode,
3069
+ childCount: firstNode.childCount + 1,
3070
+ onClick: HandleReadmeClick,
3071
+ onScroll: HandleReadmeScroll,
3072
+ onSelectionChange: HandleSelectionChange
3073
+ }, ...extraDom, ...rest];
3074
+ };
2854
3075
  const getMarkdownVirtualDom = async (html, options) => {
2855
3076
  string(html);
2856
- const dom = await getVirtualDom(html);
3077
+ const dom = addMarkdownImageErrorHandlers(await getVirtualDom(html));
2857
3078
  if (options?.scrollToTopEnabled) {
2858
- const [firstNode, ...rest] = dom;
2859
- const extraDom = getScrollToTopVirtualDom();
2860
- return [{
2861
- ...firstNode,
2862
- childCount: firstNode.childCount + 1,
2863
- onClick: HandleReadmeClick,
2864
- onScroll: HandleReadmeScroll,
2865
- onSelectionChange: HandleSelectionChange
2866
- }, ...extraDom, ...rest];
3079
+ return addScrollToTopVirtualDom(dom);
2867
3080
  }
2868
3081
  return dom;
2869
3082
  };
@@ -3223,10 +3436,67 @@ const toCommandId = key => {
3223
3436
  return key.slice(dotIndex + 1);
3224
3437
  };
3225
3438
  const create$1 = () => {
3439
+ const commandQueues = new Map();
3440
+ const generations = Object.create(null);
3226
3441
  const states = Object.create(null);
3227
3442
  const commandMapRef = {};
3443
+ const getGeneration = uid => generations[uid] || 0;
3444
+ const isCurrentGeneration = (uid, generation) => {
3445
+ return states[uid] !== undefined && getGeneration(uid) === generation;
3446
+ };
3447
+ const updateState = (uid, generation, fallbackState, updater) => {
3448
+ if (!isCurrentGeneration(uid, generation)) {
3449
+ return Promise.resolve(fallbackState);
3450
+ }
3451
+ const current = states[uid];
3452
+ const updatedState = updater(current.newState);
3453
+ if (updatedState !== current.newState) {
3454
+ states[uid] = {
3455
+ newState: updatedState,
3456
+ oldState: current.oldState,
3457
+ scheduledState: updatedState
3458
+ };
3459
+ }
3460
+ return Promise.resolve(updatedState);
3461
+ };
3462
+ const createAsyncCommandContext = (uid, generation) => {
3463
+ let latestState = states[uid].newState;
3464
+ return {
3465
+ getState: () => {
3466
+ if (isCurrentGeneration(uid, generation)) {
3467
+ latestState = states[uid].newState;
3468
+ }
3469
+ return latestState;
3470
+ },
3471
+ updateState: async updater => {
3472
+ latestState = await updateState(uid, generation, latestState, updater);
3473
+ return latestState;
3474
+ }
3475
+ };
3476
+ };
3477
+ const enqueueCommand = async (uid, command) => {
3478
+ const previous = commandQueues.get(uid) || Promise.resolve();
3479
+ const run = async () => {
3480
+ try {
3481
+ await previous;
3482
+ } catch {
3483
+ // The previous caller receives its error; later commands must still run.
3484
+ }
3485
+ await command();
3486
+ };
3487
+ const current = run();
3488
+ commandQueues.set(uid, current);
3489
+ try {
3490
+ await current;
3491
+ } finally {
3492
+ if (commandQueues.get(uid) === current) {
3493
+ commandQueues.delete(uid);
3494
+ }
3495
+ }
3496
+ };
3228
3497
  return {
3229
3498
  clear() {
3499
+ commandQueues.clear();
3230
3500
  for (const key of Object.keys(states)) {
3231
3501
  delete states[key];
3232
3502
  }
@@ -3246,6 +3516,7 @@ const create$1 = () => {
3246
3516
  return diffResult;
3247
3517
  },
3248
3518
  dispose(uid) {
3519
+ commandQueues.delete(uid);
3249
3520
  delete states[uid];
3250
3521
  },
3251
3522
  get(uid) {
@@ -3263,14 +3534,27 @@ const create$1 = () => {
3263
3534
  Object.assign(commandMapRef, commandMap);
3264
3535
  },
3265
3536
  set(uid, oldState, newState, scheduledState) {
3537
+ const current = states[uid];
3538
+ if (!current || oldState === newState && newState !== current.newState) {
3539
+ generations[uid] = getGeneration(uid) + 1;
3540
+ }
3266
3541
  states[uid] = {
3267
3542
  newState,
3268
3543
  oldState,
3269
3544
  scheduledState: scheduledState ?? newState
3270
3545
  };
3271
3546
  },
3547
+ wrapAsyncCommand(fn) {
3548
+ const wrapped = async (uid, ...args) => {
3549
+ const generation = getGeneration(uid);
3550
+ const context = createAsyncCommandContext(uid, generation);
3551
+ await fn(context, ...args);
3552
+ };
3553
+ return wrapped;
3554
+ },
3272
3555
  wrapCommand(fn) {
3273
3556
  const wrapped = async (uid, ...args) => {
3557
+ const generation = getGeneration(uid);
3274
3558
  const {
3275
3559
  newState,
3276
3560
  oldState
@@ -3279,6 +3563,9 @@ const create$1 = () => {
3279
3563
  if (oldState === newerState || newState === newerState) {
3280
3564
  return;
3281
3565
  }
3566
+ if (!isCurrentGeneration(uid, generation)) {
3567
+ return;
3568
+ }
3282
3569
  const latestOld = states[uid];
3283
3570
  const latestNew = {
3284
3571
  ...latestOld.newState,
@@ -3303,6 +3590,7 @@ const create$1 = () => {
3303
3590
  },
3304
3591
  wrapLoadContent(fn) {
3305
3592
  const wrapped = async (uid, ...args) => {
3593
+ const generation = getGeneration(uid);
3306
3594
  const {
3307
3595
  newState,
3308
3596
  oldState
@@ -3317,6 +3605,11 @@ const create$1 = () => {
3317
3605
  error
3318
3606
  };
3319
3607
  }
3608
+ if (!isCurrentGeneration(uid, generation)) {
3609
+ return {
3610
+ error
3611
+ };
3612
+ }
3320
3613
  const latestOld = states[uid];
3321
3614
  const latestNew = {
3322
3615
  ...latestOld.newState,
@@ -3332,6 +3625,51 @@ const create$1 = () => {
3332
3625
  };
3333
3626
  };
3334
3627
  return wrapped;
3628
+ },
3629
+ wrapSerialAsyncCommand(fn) {
3630
+ const wrapped = async (uid, ...args) => {
3631
+ await enqueueCommand(uid, async () => {
3632
+ if (!states[uid]) {
3633
+ return;
3634
+ }
3635
+ const generation = getGeneration(uid);
3636
+ const context = createAsyncCommandContext(uid, generation);
3637
+ await fn(context, ...args);
3638
+ });
3639
+ };
3640
+ return wrapped;
3641
+ },
3642
+ wrapSerialCommand(fn) {
3643
+ const wrapped = async (uid, ...args) => {
3644
+ await enqueueCommand(uid, async () => {
3645
+ if (!states[uid]) {
3646
+ return;
3647
+ }
3648
+ const generation = getGeneration(uid);
3649
+ const {
3650
+ newState,
3651
+ oldState
3652
+ } = states[uid];
3653
+ const newerState = await fn(newState, ...args);
3654
+ if (oldState === newerState || newState === newerState) {
3655
+ return;
3656
+ }
3657
+ if (!isCurrentGeneration(uid, generation)) {
3658
+ return;
3659
+ }
3660
+ const latestOld = states[uid];
3661
+ const latestNew = {
3662
+ ...latestOld.newState,
3663
+ ...newerState
3664
+ };
3665
+ states[uid] = {
3666
+ newState: latestNew,
3667
+ oldState: latestOld.oldState,
3668
+ scheduledState: latestNew
3669
+ };
3670
+ });
3671
+ };
3672
+ return wrapped;
3335
3673
  }
3336
3674
  };
3337
3675
  };
@@ -3431,6 +3769,8 @@ const create = (uid, uri, x, y, width, height, platform, assetDir) => {
3431
3769
  disabled: false,
3432
3770
  displaySize: '',
3433
3771
  downloadCount: 'n/a',
3772
+ errorMessage: '',
3773
+ errorTitle: '',
3434
3774
  extension: {},
3435
3775
  extensionId: '',
3436
3776
  extensionUri: '',
@@ -3703,6 +4043,96 @@ const getMenus = () => {
3703
4043
  }];
3704
4044
  };
3705
4045
 
4046
+ let mock;
4047
+ const getPage = url => {
4048
+ return Number(new URL(url).searchParams.get('page')) || 1;
4049
+ };
4050
+ const createGeneratedReleases = (url, count) => {
4051
+ const page = getPage(url);
4052
+ const start = (page - 1) * 100;
4053
+ const end = Math.min(start + 100, count);
4054
+ return Array.from({
4055
+ length: Math.max(0, end - start)
4056
+ }, (_, index) => {
4057
+ const releaseNumber = count - start - index;
4058
+ return {
4059
+ body: `Release notes for version ${releaseNumber}`,
4060
+ html_url: `https://github.com/test-owner/test-repository/releases/tag/v${releaseNumber}`,
4061
+ name: `Version ${releaseNumber}`,
4062
+ published_at: '2026-01-01T00:00:00Z',
4063
+ tag_name: `v${releaseNumber}`
4064
+ };
4065
+ });
4066
+ };
4067
+ const createMockResponse = (url, value) => {
4068
+ const status = value.status ?? 200;
4069
+ const statusText = value.statusText || getStatusText(status);
4070
+ const headers = new Headers(value.headers);
4071
+ if (value.type === 'invalid-json') {
4072
+ headers.set('content-type', 'text/html');
4073
+ return new Response('<!doctype html><title>Unexpected response</title>', {
4074
+ headers,
4075
+ status,
4076
+ statusText
4077
+ });
4078
+ }
4079
+ const body = value.type === 'generated' ? createGeneratedReleases(url, value.releaseCount ?? 0) : value.body ?? [];
4080
+ headers.set('content-type', 'application/json');
4081
+ return Response.json(body, {
4082
+ headers,
4083
+ status,
4084
+ statusText
4085
+ });
4086
+ };
4087
+ const getStatusText = status => {
4088
+ switch (status) {
4089
+ case 400:
4090
+ return 'Bad Request';
4091
+ case 401:
4092
+ return 'Unauthorized';
4093
+ case 403:
4094
+ return 'Forbidden';
4095
+ case 404:
4096
+ return 'Not Found';
4097
+ case 429:
4098
+ return 'Too Many Requests';
4099
+ case 500:
4100
+ return 'Internal Server Error';
4101
+ case 503:
4102
+ return 'Service Unavailable';
4103
+ default:
4104
+ return '';
4105
+ }
4106
+ };
4107
+ const request = url => {
4108
+ if (!mock) {
4109
+ return fetch(url, {
4110
+ headers: {
4111
+ Accept: 'application/vnd.github+json',
4112
+ 'X-GitHub-Api-Version': '2022-11-28'
4113
+ }
4114
+ });
4115
+ }
4116
+ if (mock.type === 'network-error') {
4117
+ return Promise.reject(new TypeError(mock.message || 'Failed to fetch'));
4118
+ }
4119
+ return Promise.resolve(createMockResponse(url, mock));
4120
+ };
4121
+ const mockGithubApi = value => {
4122
+ mock = value;
4123
+ };
4124
+ const handleMockGithubApi = (state, value) => {
4125
+ mockGithubApi(value);
4126
+ return state;
4127
+ };
4128
+ const resetGithubApiMock = () => {
4129
+ mock = undefined;
4130
+ };
4131
+ const handleResetGithubApiMock = state => {
4132
+ resetGithubApiMock();
4133
+ return state;
4134
+ };
4135
+
3706
4136
  const show2 = async (uid, menuId, x, y, args) => {
3707
4137
  await showContextMenu2(uid, menuId, x, y, args);
3708
4138
  };
@@ -3900,6 +4330,15 @@ const handleClickFeatures = async (state, name) => {
3900
4330
  };
3901
4331
 
3902
4332
  const handleClickScrollToTop = state => {
4333
+ if (state.selectedTab === Changelog) {
4334
+ if (state.changelogScrollTop === 0) {
4335
+ return state;
4336
+ }
4337
+ return {
4338
+ ...state,
4339
+ changelogScrollTop: 0
4340
+ };
4341
+ }
3903
4342
  const {
3904
4343
  readmeScrollTop
3905
4344
  } = state;
@@ -4033,6 +4472,16 @@ const getCurrentColorTheme = async () => {
4033
4472
  return '';
4034
4473
  };
4035
4474
 
4475
+ const getErrorMessage$1 = error => {
4476
+ if (error instanceof Error && error.message) {
4477
+ return error.message;
4478
+ }
4479
+ if (typeof error === 'string' && error) {
4480
+ return error;
4481
+ }
4482
+ return 'Unknown error';
4483
+ };
4484
+
4036
4485
  const getExtensionIdFromUri = uri => {
4037
4486
  const id = uri.slice('extension-detail://'.length);
4038
4487
  return id;
@@ -4045,6 +4494,47 @@ const getExtensionUri = (uri, platform, origin) => {
4045
4494
  return new URL(uri, origin).href;
4046
4495
  };
4047
4496
 
4497
+ const segmentRegex = /^[\w.-]+$/;
4498
+ const getRepositoryUrl = extension => {
4499
+ if (!extension || !hasProperty(extension, 'repository')) {
4500
+ return '';
4501
+ }
4502
+ const {
4503
+ repository
4504
+ } = extension;
4505
+ if (typeof repository === 'string') {
4506
+ return repository;
4507
+ }
4508
+ if (repository && hasProperty(repository, 'url') && typeof repository.url === 'string') {
4509
+ return repository.url;
4510
+ }
4511
+ return '';
4512
+ };
4513
+ const getGithubRepository = extension => {
4514
+ const rawUrl = getRepositoryUrl(extension).replace(/^git\+/, '');
4515
+ try {
4516
+ const url = new URL(rawUrl);
4517
+ if (url.protocol !== 'https:' || url.hostname.toLowerCase() !== 'github.com' || url.username || url.password || url.port) {
4518
+ return undefined;
4519
+ }
4520
+ const segments = url.pathname.split('/').filter(Boolean);
4521
+ if (segments.length !== 2) {
4522
+ return undefined;
4523
+ }
4524
+ const owner = segments[0];
4525
+ const repository = segments[1].replace(/\.git$/i, '');
4526
+ if (!owner || !repository || !segmentRegex.test(owner) || !segmentRegex.test(repository)) {
4527
+ return undefined;
4528
+ }
4529
+ return {
4530
+ owner,
4531
+ repository
4532
+ };
4533
+ } catch {
4534
+ return undefined;
4535
+ }
4536
+ };
4537
+
4048
4538
  const getLinkProtectionEnabled = async () => {
4049
4539
  try {
4050
4540
  const setting = await getPreference('application.linkProtectionEnabled');
@@ -4507,23 +4997,8 @@ const formatCreated = (created, now = Date.now()) => {
4507
4997
  return relativeTimeFormat.format(value, unit);
4508
4998
  };
4509
4999
 
4510
- const formatLastUpdated = lastUpdated => {
4511
- if (lastUpdated === null) {
4512
- return 'n/a';
4513
- }
4514
- try {
4515
- const date = new Date(lastUpdated * 1000);
4516
- if (Number.isNaN(date.getTime())) {
4517
- return 'n/a';
4518
- }
4519
- return date.toLocaleDateString(undefined, {
4520
- day: 'numeric',
4521
- month: 'short',
4522
- year: 'numeric'
4523
- });
4524
- } catch {
4525
- return 'n/a';
4526
- }
5000
+ const formatLastUpdated = (lastUpdated, now = Date.now()) => {
5001
+ return formatCreated(lastUpdated, now);
4527
5002
  };
4528
5003
 
4529
5004
  const getSizeEntries = (showSizeLink, displaySize, extensionUri) => {
@@ -4704,6 +5179,16 @@ const parseCreated = extension => {
4704
5179
  return timestamp;
4705
5180
  };
4706
5181
 
5182
+ const secondsToMillisecondsThreshold = 10_000_000_000;
5183
+ const normalizeTimestamp = timestamp => {
5184
+ if (!Number.isFinite(timestamp) || timestamp <= 0) {
5185
+ return null;
5186
+ }
5187
+ if (timestamp < secondsToMillisecondsThreshold) {
5188
+ return timestamp * 1000;
5189
+ }
5190
+ return timestamp;
5191
+ };
4707
5192
  const parseLastUpdated = extension => {
4708
5193
  if (!extension || typeof extension !== 'object') {
4709
5194
  return null;
@@ -4715,16 +5200,14 @@ const parseLastUpdated = extension => {
4715
5200
  return null;
4716
5201
  }
4717
5202
  if (typeof lastUpdated === 'number') {
4718
- if (Number.isFinite(lastUpdated) && lastUpdated > 0) {
4719
- return lastUpdated;
4720
- }
4721
- return null;
5203
+ return normalizeTimestamp(lastUpdated);
4722
5204
  }
4723
5205
  if (typeof lastUpdated === 'string') {
4724
- const parsed = Number.parseFloat(lastUpdated);
4725
- if (Number.isFinite(parsed) && parsed > 0) {
4726
- return parsed;
5206
+ const numericTimestamp = Number(lastUpdated);
5207
+ if (Number.isFinite(numericTimestamp)) {
5208
+ return normalizeTimestamp(numericTimestamp);
4727
5209
  }
5210
+ return normalizeTimestamp(Date.parse(lastUpdated));
4728
5211
  }
4729
5212
  return null;
4730
5213
  };
@@ -4777,7 +5260,7 @@ const restoreState = savedState => {
4777
5260
  const isEnabled = tab => {
4778
5261
  return tab.enabled;
4779
5262
  };
4780
- const loadContent = async (state, platform, savedState, isTest = false) => {
5263
+ const loadContentInternal = async (state, platform, savedState, isTest = false) => {
4781
5264
  if (isTest) {
4782
5265
  savedState = undefined;
4783
5266
  }
@@ -4839,7 +5322,8 @@ const loadContent = async (state, platform, savedState, isTest = false) => {
4839
5322
  } = restoreState(savedState);
4840
5323
  const features = getFeatures(selectedFeature || Theme, extension);
4841
5324
  const hasFeatures = features.length > 0;
4842
- const tabs = getTabs(selectedTab, hasReadme, hasFeatures, hasChangelog);
5325
+ const hasGithubReleases = Boolean(getGithubRepository(extension));
5326
+ const tabs = getTabs(selectedTab, hasReadme, hasFeatures, hasChangelog || hasGithubReleases);
4843
5327
  const enabledTabs = tabs.filter(isEnabled);
4844
5328
  const sizeValue = getViewletSize(width || 0);
4845
5329
  const showSizeLink = platform !== Web$1;
@@ -4872,6 +5356,8 @@ const loadContent = async (state, platform, savedState, isTest = false) => {
4872
5356
  disabled,
4873
5357
  displaySize,
4874
5358
  downloadCount,
5359
+ errorMessage: '',
5360
+ errorTitle: '',
4875
5361
  extension,
4876
5362
  extensionId,
4877
5363
  extensionUri,
@@ -4908,6 +5394,21 @@ const loadContent = async (state, platform, savedState, isTest = false) => {
4908
5394
  tabs: enabledTabs
4909
5395
  };
4910
5396
  };
5397
+ const loadContent = async (state, platform, savedState, isTest = false) => {
5398
+ try {
5399
+ return await loadContentInternal(state, platform, savedState, isTest);
5400
+ } catch (error) {
5401
+ const extensionId = getExtensionIdFromUri(state.uri);
5402
+ const errorMessage = error instanceof ExtensionNotFoundError ? extensionNotAvailable(extensionId) : unableToLoadExtensionWithError(getErrorMessage$1(error));
5403
+ return {
5404
+ ...state,
5405
+ errorMessage,
5406
+ errorTitle: unableToLoadExtension(),
5407
+ extensionId,
5408
+ initial: false
5409
+ };
5410
+ }
5411
+ };
4911
5412
 
4912
5413
  const handleExtensionsChanged = async state => {
4913
5414
  const {
@@ -4927,6 +5428,10 @@ const handleExtensionsStatusUpdate = async state => {
4927
5428
  };
4928
5429
  };
4929
5430
 
5431
+ const handleHeaderContextMenu = async state => {
5432
+ return state;
5433
+ };
5434
+
4930
5435
  const handleIconError = state => {
4931
5436
  const {
4932
5437
  assetDir,
@@ -4953,6 +5458,51 @@ const handleImageContextMenu = async (state, eventX, eventY) => {
4953
5458
  return state;
4954
5459
  };
4955
5460
 
5461
+ const getMarkdownImageErrorVirtualDom = () => {
5462
+ return [{
5463
+ childCount: 1,
5464
+ className: MarkdownImageError,
5465
+ type: Span
5466
+ }, text(imageCannotBeLoaded())];
5467
+ };
5468
+
5469
+ const isMatchingImageSrc = (nodeSrc, failedSrc) => {
5470
+ if (typeof nodeSrc !== 'string' || !failedSrc) {
5471
+ return false;
5472
+ }
5473
+ const normalizedNodeSrc = nodeSrc.startsWith('./') ? nodeSrc.slice(2) : nodeSrc;
5474
+ return nodeSrc === failedSrc || failedSrc.endsWith(nodeSrc) || nodeSrc.endsWith(failedSrc) || failedSrc.endsWith(`/${normalizedNodeSrc}`);
5475
+ };
5476
+ const replaceMarkdownImageWithError = (dom, failedSrc) => {
5477
+ let replaced = false;
5478
+ const newDom = [];
5479
+ for (const node of dom) {
5480
+ if (node.type === Img && isMatchingImageSrc(node.src, failedSrc)) {
5481
+ newDom.push(...getMarkdownImageErrorVirtualDom());
5482
+ replaced = true;
5483
+ } else {
5484
+ newDom.push(node);
5485
+ }
5486
+ }
5487
+ if (!replaced) {
5488
+ return dom;
5489
+ }
5490
+ return newDom;
5491
+ };
5492
+
5493
+ const handleMarkdownImageError = (state, failedSrc) => {
5494
+ const detailsVirtualDom = replaceMarkdownImageWithError(state.detailsVirtualDom, failedSrc);
5495
+ const changelogVirtualDom = replaceMarkdownImageWithError(state.changelogVirtualDom, failedSrc);
5496
+ if (detailsVirtualDom === state.detailsVirtualDom && changelogVirtualDom === state.changelogVirtualDom) {
5497
+ return state;
5498
+ }
5499
+ return {
5500
+ ...state,
5501
+ changelogVirtualDom,
5502
+ detailsVirtualDom
5503
+ };
5504
+ };
5505
+
4956
5506
  const previewColorTheme = async colorThemeId => {
4957
5507
  await invoke$1('ColorTheme.previewColorTheme', colorThemeId);
4958
5508
  };
@@ -5057,6 +5607,13 @@ const handleResourceLinkClick = async (state, href) => {
5057
5607
 
5058
5608
  const handleScroll = (state, scrollTop, scrollSource = Script) => {
5059
5609
  const newScrollTop = Math.max(0, scrollTop);
5610
+ if (state.selectedTab === Changelog) {
5611
+ return {
5612
+ ...state,
5613
+ changelogScrollTop: newScrollTop,
5614
+ scrollSource
5615
+ };
5616
+ }
5060
5617
  return {
5061
5618
  ...state,
5062
5619
  readmeScrollTop: newScrollTop,
@@ -5083,6 +5640,39 @@ const handleTabFocus = (state, name) => {
5083
5640
  };
5084
5641
  };
5085
5642
 
5643
+ const escapeMarkdown = value => {
5644
+ return value.replaceAll('\\', '\\\\').replaceAll('[', '\\[').replaceAll(']', '\\]');
5645
+ };
5646
+ const getPublishedText = publishedAt => {
5647
+ if (!publishedAt) {
5648
+ return 'Publication date unavailable';
5649
+ }
5650
+ const date = new Date(publishedAt);
5651
+ if (Number.isNaN(date.getTime())) {
5652
+ return 'Publication date unavailable';
5653
+ }
5654
+ return `Published ${date.toLocaleDateString(undefined, {
5655
+ dateStyle: 'long'
5656
+ })}`;
5657
+ };
5658
+ const getGithubReleasesMarkdown = (releases, githubRepository) => {
5659
+ if (releases.length === 0) {
5660
+ return `# Changelog\n\nNo GitHub releases were found for **${escapeMarkdown(githubRepository.owner)}/${escapeMarkdown(githubRepository.repository)}**.`;
5661
+ }
5662
+ return releases.map(release => {
5663
+ const title = release.name || release.tagName;
5664
+ const body = release.body.trim() || '_No release notes were provided._';
5665
+ return `# [${escapeMarkdown(title)}](${release.htmlUrl})\n\n${getPublishedText(release.publishedAt)} · \`${release.tagName}\`\n\n${body}`;
5666
+ }).join('\n\n---\n\n');
5667
+ };
5668
+
5669
+ class GithubReleasesError extends Error {
5670
+ constructor(message) {
5671
+ super(message);
5672
+ this.name = 'GithubReleasesError';
5673
+ }
5674
+ }
5675
+
5086
5676
  const error = async error => {
5087
5677
  // TODO send message to error worker or log worker
5088
5678
  // @ts-ignore
@@ -5103,21 +5693,206 @@ const loadChangelogContent = async path => {
5103
5693
  }
5104
5694
  };
5105
5695
 
5696
+ const pageSize = 100;
5697
+ const maximumReleases = 100;
5698
+ const getErrorMessage = async response => {
5699
+ try {
5700
+ const body = await response.clone().json();
5701
+ if (body && hasProperty(body, 'message') && typeof body.message === 'string') {
5702
+ return body.message;
5703
+ }
5704
+ } catch {
5705
+ // The status code still provides a useful error when the error body is invalid.
5706
+ }
5707
+ return '';
5708
+ };
5709
+ const getHttpError = async response => {
5710
+ const apiMessage = await getErrorMessage(response);
5711
+ const rateLimitRemaining = response.headers.get('x-ratelimit-remaining');
5712
+ if (response.status === 429 || response.status === 403 && (rateLimitRemaining === '0' || /rate limit/i.test(apiMessage))) {
5713
+ const resetValue = Number(response.headers.get('x-ratelimit-reset'));
5714
+ const retry = Number.isFinite(resetValue) && resetValue > 0 ? ` Try again after ${new Date(resetValue * 1000).toLocaleString()}.` : ' Please try again later.';
5715
+ return new GithubReleasesError(`GitHub API rate limit exceeded.${retry}`);
5716
+ }
5717
+ if (response.status === 404) {
5718
+ return new GithubReleasesError('The GitHub repository could not be found or is not publicly reachable.');
5719
+ }
5720
+ if (response.status >= 500) {
5721
+ return new GithubReleasesError(`GitHub is temporarily unavailable (server error ${response.status}). Please try again later.`);
5722
+ }
5723
+ if (response.status >= 400) {
5724
+ const detail = apiMessage ? `: ${apiMessage}` : '';
5725
+ return new GithubReleasesError(`GitHub rejected the releases request (${response.status} ${response.statusText || 'Client Error'})${detail}.`);
5726
+ }
5727
+ return new GithubReleasesError(`GitHub returned an unexpected HTTP status (${response.status}).`);
5728
+ };
5729
+ const parseNullableString = value => {
5730
+ if (typeof value === 'string') {
5731
+ return value;
5732
+ }
5733
+ if (value === null) {
5734
+ return '';
5735
+ }
5736
+ return undefined;
5737
+ };
5738
+ const parseRelease = value => {
5739
+ if (!value || !hasProperty(value, 'body') || !hasProperty(value, 'html_url') || !hasProperty(value, 'name') || !hasProperty(value, 'published_at') || !hasProperty(value, 'tag_name')) {
5740
+ return undefined;
5741
+ }
5742
+ const body = parseNullableString(value.body);
5743
+ const name = parseNullableString(value.name);
5744
+ const publishedAt = parseNullableString(value.published_at);
5745
+ if (body === undefined || name === undefined || publishedAt === undefined || typeof value.html_url !== 'string' || typeof value.tag_name !== 'string') {
5746
+ return undefined;
5747
+ }
5748
+ try {
5749
+ const htmlUrl = new URL(value.html_url);
5750
+ if (htmlUrl.protocol !== 'https:' || htmlUrl.hostname !== 'github.com') {
5751
+ return undefined;
5752
+ }
5753
+ } catch {
5754
+ return undefined;
5755
+ }
5756
+ return {
5757
+ body,
5758
+ htmlUrl: value.html_url,
5759
+ name,
5760
+ publishedAt,
5761
+ tagName: value.tag_name
5762
+ };
5763
+ };
5764
+ const parsePage = async response => {
5765
+ let value;
5766
+ try {
5767
+ value = await response.json();
5768
+ } catch {
5769
+ throw new GithubReleasesError('GitHub returned an invalid response instead of JSON release data. Please try again later.');
5770
+ }
5771
+ if (!Array.isArray(value)) {
5772
+ throw new GithubReleasesError('GitHub returned invalid release data. Please try again later.');
5773
+ }
5774
+ const releases = value.map(parseRelease);
5775
+ if (releases.some(release => !release)) {
5776
+ throw new GithubReleasesError('GitHub returned invalid release data. Please try again later.');
5777
+ }
5778
+ return releases;
5779
+ };
5780
+ const loadGithubReleases = async ({
5781
+ owner,
5782
+ repository
5783
+ }) => {
5784
+ const releases = [];
5785
+ for (let page = 1;; page++) {
5786
+ const url = `https://api.github.com/repos/${encodeURIComponent(owner)}/${encodeURIComponent(repository)}/releases?per_page=${pageSize}&page=${page}`;
5787
+ let response;
5788
+ try {
5789
+ response = await request(url);
5790
+ } catch {
5791
+ throw new GithubReleasesError('GitHub releases could not be loaded because GitHub is not reachable. Check your connection and try again.');
5792
+ }
5793
+ if (!response.ok) {
5794
+ throw await getHttpError(response);
5795
+ }
5796
+ const pageReleases = await parsePage(response);
5797
+ const remaining = maximumReleases - releases.length;
5798
+ if (pageReleases.length > remaining) {
5799
+ releases.push(...pageReleases.slice(0, remaining));
5800
+ return {
5801
+ isTruncated: true,
5802
+ releases
5803
+ };
5804
+ }
5805
+ releases.push(...pageReleases);
5806
+ if (pageReleases.length < pageSize) {
5807
+ return {
5808
+ isTruncated: false,
5809
+ releases
5810
+ };
5811
+ }
5812
+ }
5813
+ };
5814
+
5815
+ const releaseBatchSize = 100;
5816
+ const mergeMarkdownVirtualDoms = chunks => {
5817
+ let root;
5818
+ const children = [];
5819
+ for (const chunk of chunks) {
5820
+ const [chunkRoot, ...chunkChildren] = chunk;
5821
+ if (!chunkRoot) {
5822
+ continue;
5823
+ }
5824
+ root = root ? {
5825
+ ...root,
5826
+ childCount: root.childCount + chunkRoot.childCount
5827
+ } : chunkRoot;
5828
+ children.push(...chunkChildren);
5829
+ }
5830
+ return root ? [root, ...children] : [];
5831
+ };
5832
+ const renderGithubReleases = async (result, githubRepository, languages, locationProtocol) => {
5833
+ const chunks = [];
5834
+ const {
5835
+ isTruncated,
5836
+ releases
5837
+ } = result;
5838
+ const releaseGroups = releases.length === 0 ? [[]] : Array.from({
5839
+ length: Math.ceil(releases.length / releaseBatchSize)
5840
+ }, (_, index) => {
5841
+ return releases.slice(index * releaseBatchSize, (index + 1) * releaseBatchSize);
5842
+ });
5843
+ const baseUrl = `https://github.com/${githubRepository.owner}/${githubRepository.repository}/blob/HEAD/`;
5844
+ for (const [index, releaseGroup] of releaseGroups.entries()) {
5845
+ const limitMessage = index === 0 && isTruncated ? `> Showing the newest ${releases.length} GitHub releases. Older releases are not displayed to keep the editor responsive.\n\n` : '';
5846
+ const markdown = limitMessage + getGithubReleasesMarkdown(releaseGroup, githubRepository);
5847
+ const html = await renderMarkdown(markdown, {
5848
+ baseUrl,
5849
+ languages,
5850
+ linksExternal: true,
5851
+ locationProtocol
5852
+ });
5853
+ chunks.push([...(await getMarkdownVirtualDom(html))]);
5854
+ }
5855
+ return addScrollToTopVirtualDom(mergeMarkdownVirtualDoms(chunks));
5856
+ };
5106
5857
  const selectTabChangelog = async state => {
5107
5858
  const {
5108
5859
  baseUrl,
5860
+ extension,
5109
5861
  extensionUri,
5110
5862
  languages,
5111
5863
  locationProtocol,
5112
5864
  tabs
5113
5865
  } = state;
5114
- const changelogContent = await loadChangelogContent(extensionUri);
5115
- const changelogMarkdownHtml = await renderMarkdown(changelogContent, {
5116
- baseUrl,
5117
- languages,
5118
- locationProtocol
5119
- });
5120
- const changelogDom = await getMarkdownVirtualDom(changelogMarkdownHtml);
5866
+ const githubRepository = getGithubRepository(extension);
5867
+ let changelogDom;
5868
+ if (githubRepository) {
5869
+ try {
5870
+ const result = await loadGithubReleases(githubRepository);
5871
+ changelogDom = await renderGithubReleases(result, githubRepository, languages, locationProtocol);
5872
+ } catch (error) {
5873
+ const message = error instanceof GithubReleasesError ? error.message : 'GitHub releases could not be loaded. Please try again later.';
5874
+ const html = await renderMarkdown(`# Changelog\n\n${message}`, {
5875
+ baseUrl,
5876
+ languages,
5877
+ linksExternal: true,
5878
+ locationProtocol
5879
+ });
5880
+ changelogDom = await getMarkdownVirtualDom(html, {
5881
+ scrollToTopEnabled: true
5882
+ });
5883
+ }
5884
+ } else {
5885
+ const changelogContent = await loadChangelogContent(extensionUri);
5886
+ const html = await renderMarkdown(changelogContent, {
5887
+ baseUrl,
5888
+ languages,
5889
+ linksExternal: true,
5890
+ locationProtocol
5891
+ });
5892
+ changelogDom = await getMarkdownVirtualDom(html, {
5893
+ scrollToTopEnabled: true
5894
+ });
5895
+ }
5121
5896
  const newTabs = tabs.map(tab => {
5122
5897
  return {
5123
5898
  ...tab,
@@ -5391,7 +6166,8 @@ const getChangelogVirtualDom = changelogDom => {
5391
6166
  // TODO set tabpanel role
5392
6167
  return [{
5393
6168
  childCount: 1,
5394
- className: Changelog$1,
6169
+ className: `${ExtensionDetailPanel} ${Changelog$1}`,
6170
+ role: Panel,
5395
6171
  type: Div
5396
6172
  }, ...changelogDom];
5397
6173
  };
@@ -5695,6 +6471,31 @@ const getExtensionDetailContentVirtualDom = (sanitizedReadmeHtml, selectedTab, w
5695
6471
  }
5696
6472
  };
5697
6473
 
6474
+ const getExtensionDetailErrorVirtualDom = (title, message) => {
6475
+ return [{
6476
+ childCount: 1,
6477
+ className: mergeClassNames(Viewlet, ExtensionDetail, ExtensionDetailError),
6478
+ type: Div
6479
+ }, {
6480
+ childCount: 3,
6481
+ className: ExtensionDetailErrorCard,
6482
+ role: 'alert',
6483
+ type: Div
6484
+ }, {
6485
+ childCount: 0,
6486
+ className: mergeClassNames(MaskIcon, MaskIconWarning, ExtensionDetailErrorIcon),
6487
+ type: Div
6488
+ }, {
6489
+ childCount: 1,
6490
+ className: ExtensionDetailErrorTitle,
6491
+ type: H1
6492
+ }, text(title), {
6493
+ childCount: 1,
6494
+ className: ExtensionDetailErrorMessage,
6495
+ type: P
6496
+ }, text(message)];
6497
+ };
6498
+
5698
6499
  const getExtensionDetailDescriptionVirtualDom = description => {
5699
6500
  return [{
5700
6501
  childCount: 1,
@@ -5764,6 +6565,24 @@ const getExtensionDetailIconVirtualDom = iconSrc => {
5764
6565
  };
5765
6566
  };
5766
6567
 
6568
+ const getStatisticVirtualDom = (label, value, className) => {
6569
+ const accessibleLabel = `${label}: ${value}`;
6570
+ return [{
6571
+ ariaLabel: accessibleLabel,
6572
+ childCount: 1,
6573
+ className: mergeClassNames(ExtensionDetailStatistic, className),
6574
+ title: accessibleLabel,
6575
+ type: Span
6576
+ }, text(value)];
6577
+ };
6578
+ const getExtensionDetailMetadataVirtualDom = (downloadCount$1, rating$1) => {
6579
+ return [{
6580
+ childCount: 2,
6581
+ className: ExtensionDetailMetadata,
6582
+ type: Div
6583
+ }, ...getStatisticVirtualDom(downloadCount(), downloadCount$1, ExtensionDetailDownloadCount), ...getStatisticVirtualDom(rating(), rating$1, ExtensionDetailRating)];
6584
+ };
6585
+
5767
6586
  const getNameBadgeVirtualDom = badge => {
5768
6587
  if (!badge) {
5769
6588
  return [];
@@ -5799,16 +6618,17 @@ const getExtensionDetailNameVirtualDom = (name, badge) => {
5799
6618
  return getExtensionDetailNameDefaultVirtualDom(name);
5800
6619
  };
5801
6620
 
5802
- const getExtensionDetailHeaderVirtualDom = (name, iconSrc, description, badge, buttonDefs, settingsButtonEnabled) => {
6621
+ const getExtensionDetailHeaderVirtualDom = (name, iconSrc, description, badge, buttonDefs, settingsButtonEnabled, downloadCount = 'n/a', rating = 'n/a') => {
5803
6622
  const dom = [{
5804
6623
  childCount: 2,
5805
6624
  className: ExtensionDetailHeader,
5806
6625
  type: Div
5807
6626
  }, getExtensionDetailIconVirtualDom(iconSrc), {
5808
- childCount: 3,
6627
+ childCount: 4,
5809
6628
  className: ExtensionDetailHeaderDetails,
6629
+ onContextMenu: HandleHeaderContextMenu,
5810
6630
  type: Div
5811
- }, ...getExtensionDetailNameVirtualDom(name, badge), ...getExtensionDetailDescriptionVirtualDom(description), ...getExtensionDetailHeaderActionsVirtualDom(buttonDefs, settingsButtonEnabled)];
6631
+ }, ...getExtensionDetailNameVirtualDom(name, badge), ...getExtensionDetailDescriptionVirtualDom(description), ...getExtensionDetailMetadataVirtualDom(downloadCount, rating), ...getExtensionDetailHeaderActionsVirtualDom(buttonDefs, settingsButtonEnabled)];
5812
6632
  return dom;
5813
6633
  };
5814
6634
 
@@ -5863,6 +6683,9 @@ const getClassNames = size => {
5863
6683
  };
5864
6684
 
5865
6685
  const getExtensionDetailVirtualDom = (newState, selectedTab) => {
6686
+ if (newState.errorMessage) {
6687
+ return getExtensionDetailErrorVirtualDom(newState.errorTitle, newState.errorMessage);
6688
+ }
5866
6689
  // TODO move this to view model so that rendering occurs like
5867
6690
  // 1. state
5868
6691
  // 2. view model
@@ -5875,9 +6698,11 @@ const getExtensionDetailVirtualDom = (newState, selectedTab) => {
5875
6698
  changelogVirtualDom,
5876
6699
  description,
5877
6700
  detailsVirtualDom,
6701
+ downloadCount,
5878
6702
  focusedTabIndex,
5879
6703
  iconSrc,
5880
6704
  name,
6705
+ rating,
5881
6706
  resources,
5882
6707
  scrollToTopButtonEnabled,
5883
6708
  settingsButtonEnabled,
@@ -5891,7 +6716,7 @@ const getExtensionDetailVirtualDom = (newState, selectedTab) => {
5891
6716
  childCount: 3,
5892
6717
  className: mergeClassNames(Viewlet, ExtensionDetail, sizeClass),
5893
6718
  type: Div
5894
- }, ...getExtensionDetailHeaderVirtualDom(name, iconSrc, description, badge, buttons, settingsButtonEnabled), ...getTabsVirtualDom(tabs, focusedTabIndex), ...getExtensionDetailContentVirtualDom(detailsVirtualDom, selectedTab, width, scrollToTopButtonEnabled, categories, resources, showAdditionalDetailsBreakpoint, changelogVirtualDom, newState)];
6719
+ }, ...getExtensionDetailHeaderVirtualDom(name, iconSrc, description, badge, buttons, settingsButtonEnabled, downloadCount, rating), ...getTabsVirtualDom(tabs, focusedTabIndex), ...getExtensionDetailContentVirtualDom(detailsVirtualDom, selectedTab, width, scrollToTopButtonEnabled, categories, resources, showAdditionalDetailsBreakpoint, changelogVirtualDom, newState)];
5895
6720
  return dom;
5896
6721
  };
5897
6722
 
@@ -6029,6 +6854,13 @@ const renderEventListeners = () => {
6029
6854
  name: HandleImageContextMenu,
6030
6855
  params: ['handleImageContextMenu', ClientX, ClientY],
6031
6856
  preventDefault: true
6857
+ }, {
6858
+ name: HandleHeaderContextMenu,
6859
+ params: ['handleHeaderContextMenu'],
6860
+ preventDefault: true
6861
+ }, {
6862
+ name: HandleMarkdownImageError,
6863
+ params: ['handleMarkdownImageError', 'event.currentTarget.src']
6032
6864
  }, {
6033
6865
  name: HandleReadmeScroll,
6034
6866
  params: ['handleScroll', 'event.target.scrollTop', User],
@@ -6084,6 +6916,10 @@ const renderEventListeners = () => {
6084
6916
  }];
6085
6917
  };
6086
6918
 
6919
+ const renderTitle = state => {
6920
+ return state.name;
6921
+ };
6922
+
6087
6923
  const resize = (state, dimensions) => {
6088
6924
  const padding = getPadding(dimensions.width);
6089
6925
  const sideBarWidth = getSideBarWidth(dimensions.width);
@@ -6141,8 +6977,10 @@ const commandMap = {
6141
6977
  'ExtensionDetail.handleExtensionsChanged': wrapCommand(handleExtensionsChanged),
6142
6978
  'ExtensionDetail.handleExtensionsStatusUpdate': wrapCommand(handleExtensionsStatusUpdate),
6143
6979
  'ExtensionDetail.handleFeaturesClick': wrapCommand(handleClickFeatures),
6980
+ 'ExtensionDetail.handleHeaderContextMenu': wrapCommand(handleHeaderContextMenu),
6144
6981
  'ExtensionDetail.handleIconError': wrapCommand(handleIconError),
6145
6982
  'ExtensionDetail.handleImageContextMenu': wrapCommand(handleImageContextMenu),
6983
+ 'ExtensionDetail.handleMarkdownImageError': wrapCommand(handleMarkdownImageError),
6146
6984
  'ExtensionDetail.handleMouseEnterEnable': wrapCommand(handleMouseEnterEnable),
6147
6985
  'ExtensionDetail.handleMouseLeaveEnable': wrapCommand(handleMouseLeaveEnable),
6148
6986
  'ExtensionDetail.handleReadmeClick': wrapCommand(handleReadmeClick),
@@ -6157,9 +6995,12 @@ const commandMap = {
6157
6995
  'ExtensionDetail.hideSizeLink': wrapCommand(hideSizeLink),
6158
6996
  'ExtensionDetail.initialize': initialize,
6159
6997
  'ExtensionDetail.loadContent2': wrapCommand(loadContent2),
6998
+ 'ExtensionDetail.mockGithubApi': wrapCommand(handleMockGithubApi),
6160
6999
  'ExtensionDetail.openImageInNewTab': wrapCommand(openImageInNewTab),
6161
7000
  'ExtensionDetail.render2': render2,
6162
7001
  'ExtensionDetail.renderEventListeners': renderEventListeners,
7002
+ 'ExtensionDetail.renderTitle': wrapGetter(renderTitle),
7003
+ 'ExtensionDetail.resetGithubApiMock': wrapCommand(handleResetGithubApiMock),
6163
7004
  'ExtensionDetail.resize': wrapCommand(resize),
6164
7005
  'ExtensionDetail.saveState': wrapGetter(saveState),
6165
7006
  'ExtensionDetail.selectTab': wrapCommand(selectTab),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lvce-editor/extension-detail-view",
3
- "version": "7.11.0",
3
+ "version": "7.14.0",
4
4
  "description": "Extension Detail View Worker",
5
5
  "repository": {
6
6
  "type": "git",