@lvce-editor/extension-detail-view 4.5.0 → 4.7.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.
@@ -30,6 +30,7 @@ const Id = 'ID';
30
30
  const Identifier = 'Identifier';
31
31
  const ImportTime = 'Import Time: ';
32
32
  const Installation = 'Installation';
33
+ const InvalidLink = 'Invalid link';
33
34
  const Issues = 'Issues';
34
35
  const JsonValidation$1 = 'Json Validation';
35
36
  const Label = 'Label';
@@ -42,12 +43,14 @@ const NoReadmeFound = 'No Readme Found.';
42
43
  const OpenImageInNewTab = 'Open Image in New Tab';
43
44
  const OpenInNewTab = 'Open in New Tab';
44
45
  const ProgrammingLanguages$1 = 'Programming Languages';
46
+ const PropertyMustBeOfTypeString = 'Property must be a string';
45
47
  const Published = 'Published';
46
48
  const Repository = 'Repository';
47
49
  const Resources$1 = 'Resources';
48
50
  const RuntimeStatus$1 = 'Runtime Status';
49
51
  const SaveImageAs = 'Save Image as';
50
52
  const Schema = 'Schema';
53
+ const SchemaNotFound = 'Schema not found';
51
54
  const ScrollToTop$1 = 'Scroll to top';
52
55
  const SelectedFeatureUnknownOrUnsupported = 'Selected feature is unknown or unsupported';
53
56
  const Selector = 'Selector';
@@ -207,6 +210,15 @@ const repository = () => {
207
210
  const license = () => {
208
211
  return i18nString(License);
209
212
  };
213
+ const propertyMustBeOfTypeString = () => {
214
+ return i18nString(PropertyMustBeOfTypeString);
215
+ };
216
+ const schemaNotFound = () => {
217
+ return i18nString(SchemaNotFound);
218
+ };
219
+ const invalidLink = () => {
220
+ return i18nString(InvalidLink);
221
+ };
210
222
 
211
223
  const getActivationEventsDetails = async extension => {
212
224
  const activationEvents = extension.activation || [];
@@ -305,7 +317,7 @@ const Features$1 = 'Features';
305
317
  const FeaturesList = 'FeaturesList';
306
318
  const FeatureWebView = 'FeatureWebView';
307
319
  const Large$1 = 'Large';
308
- const Link = 'Link';
320
+ const Link$1 = 'Link';
309
321
  const Markdown = 'Markdown';
310
322
  const MoreInfo = 'MoreInfo';
311
323
  const MoreInfoEntry = 'MoreInfoEntry';
@@ -323,6 +335,7 @@ const SettingsIcon = 'SettingsIcon';
323
335
  const Small$1 = 'Small';
324
336
  const Table = 'Table';
325
337
  const TableCell = 'TableCell';
338
+ const TableCellInvalid = 'TableCellInvalid';
326
339
  const TableHeading = 'TableHeading';
327
340
  const Viewlet = 'Viewlet';
328
341
 
@@ -363,6 +376,7 @@ const getActivationEventsVirtualDom = state => {
363
376
 
364
377
  const Text = 1;
365
378
  const Code = 2;
379
+ const Link = 7;
366
380
 
367
381
  const getCommandTableEntry = command => {
368
382
  // TODO watch out for command being null/undefined/number/string/array
@@ -424,31 +438,58 @@ const getTableHeadingVirtualDom = heading => {
424
438
  }, text(heading)];
425
439
  };
426
440
 
427
- const getCellCodeVirtualDom = value => {
441
+ const getCellCodeVirtualDom = (value, props) => {
442
+ const tdClassName = props?.className ? `${TableCell} ${props.className}` : TableCell;
428
443
  return [{
429
444
  type: Td,
430
- className: TableCell,
431
- childCount: 1
445
+ className: tdClassName,
446
+ childCount: 1,
447
+ ...(props?.title ? {
448
+ title: props.title
449
+ } : {})
432
450
  }, {
433
451
  type: Code$2,
434
452
  childCount: 1
435
453
  }, text(value)];
436
454
  };
437
455
 
438
- const getCellTextVirtualDom = value => {
456
+ const getCellLinkVirtualDom = (value, props) => {
457
+ const tdClassName = props?.className ? `${TableCell} ${props.className}` : TableCell;
439
458
  return [{
440
459
  type: Td,
441
- className: TableCell,
460
+ className: tdClassName,
461
+ childCount: 1,
462
+ ...(props?.title ? {
463
+ title: props.title
464
+ } : {})
465
+ }, {
466
+ type: A,
467
+ className: Link$1,
468
+ href: props?.href,
442
469
  childCount: 1
443
470
  }, text(value)];
444
471
  };
445
472
 
473
+ const getCellTextVirtualDom = (value, props) => {
474
+ const tdClassName = props?.className ? `${TableCell} ${props.className}` : TableCell;
475
+ return [{
476
+ type: Td,
477
+ className: tdClassName,
478
+ childCount: 1,
479
+ ...(props?.title ? {
480
+ title: props.title
481
+ } : {})
482
+ }, text(value)];
483
+ };
484
+
446
485
  const getCellRenderer = type => {
447
486
  switch (type) {
448
487
  case Code:
449
488
  return getCellCodeVirtualDom;
450
489
  case Text:
451
490
  return getCellTextVirtualDom;
491
+ case Link:
492
+ return getCellLinkVirtualDom;
452
493
  default:
453
494
  throw new Error(`unexpected cell type ${type}`);
454
495
  }
@@ -457,10 +498,11 @@ const getCellRenderer = type => {
457
498
  const getCellVirtualDom = entry => {
458
499
  const {
459
500
  value,
460
- type
501
+ type,
502
+ ...props
461
503
  } = entry;
462
504
  const fn = getCellRenderer(type);
463
- return fn(value);
505
+ return fn(value, props);
464
506
  };
465
507
 
466
508
  const getTableRowVirtualDom = entries => {
@@ -509,24 +551,187 @@ const getCommandsVirtualDom = state => {
509
551
  return getFeatureCommandsVirtualDom(state.commands);
510
552
  };
511
553
 
512
- const getJsonValidationTableEntry = validation => {
513
- // TODO handle case when validation is invalid like null
554
+ const isExternalLink = schema => {
555
+ return schema.startsWith('http://') || schema.startsWith('https://');
556
+ };
557
+ const hasWhitespace = value => {
558
+ return /\s/.test(value);
559
+ };
560
+ const isOnlyDotsOrEmpty = value => {
561
+ const trimmed = value.trim();
562
+ return trimmed === '' || /^\.*$/.test(trimmed);
563
+ };
564
+ const isValidHttpUrl = value => {
565
+ try {
566
+ const url = new URL(value);
567
+ return (url.protocol === 'http:' || url.protocol === 'https:') && !!url.hostname;
568
+ } catch {
569
+ return false;
570
+ }
571
+ };
572
+ const isValidRelativePath = value => {
573
+ // Disallow schemes and whitespace
574
+ if (hasWhitespace(value)) {
575
+ return false;
576
+ }
577
+ if (value.includes('://')) {
578
+ return false;
579
+ }
580
+ if (isOnlyDotsOrEmpty(value)) {
581
+ return false;
582
+ }
583
+ // Allow paths like ./a.json, ../a.json, /a.json, schemas/a.json, a/b.json
584
+ if (!/^[./]?[A-Za-z0-9._\-/]+$/.test(value)) {
585
+ return false;
586
+ }
587
+ // Must contain at least one alphanumeric character
588
+ if (!/[A-Za-z0-9]/.test(value)) {
589
+ return false;
590
+ }
591
+ return true;
592
+ };
593
+ const getSchemaLinkUrl = (schema, extensionUri) => {
594
+ if (!schema || typeof schema !== 'string') {
595
+ return '';
596
+ }
597
+ const trimmed = schema.trim();
598
+ if (trimmed !== schema) {
599
+ return '';
600
+ }
601
+ if (isExternalLink(schema)) {
602
+ return isValidHttpUrl(schema) ? schema : '';
603
+ }
604
+ if (!isValidRelativePath(schema)) {
605
+ return '';
606
+ }
607
+ try {
608
+ return new URL(schema, extensionUri).toString();
609
+ } catch {
610
+ return '';
611
+ }
612
+ };
613
+
614
+ const existsJson = async schemaUrl => {
615
+ try {
616
+ // TODO verify that response header is json
617
+ const response = await fetch(schemaUrl, {
618
+ method: 'HEAD'
619
+ });
620
+ return response.ok;
621
+ } catch {
622
+ return false;
623
+ }
624
+ };
625
+ const getJsonValidationInfos = async (extensionUri, validations) => {
626
+ const validationInfos = [];
627
+ for (const validation of validations) {
628
+ const schema = validation.schema ?? validation.url;
629
+ const schemaLinkUrl = getSchemaLinkUrl(schema, extensionUri);
630
+ const {
631
+ fileMatch
632
+ } = validation;
633
+ if (typeof schema !== 'string') {
634
+ validationInfos.push({
635
+ isValid: false,
636
+ stringValue: JSON.stringify(schema),
637
+ schemaUrl: '',
638
+ errorMessage: propertyMustBeOfTypeString(),
639
+ fileMatch
640
+ });
641
+ } else if (schema && !schemaLinkUrl) {
642
+ validationInfos.push({
643
+ isValid: false,
644
+ stringValue: schema,
645
+ schemaUrl: schemaLinkUrl,
646
+ errorMessage: invalidLink(),
647
+ fileMatch
648
+ });
649
+ } else if (schemaLinkUrl) {
650
+ if (await existsJson(schemaLinkUrl)) {
651
+ validationInfos.push({
652
+ isValid: true,
653
+ stringValue: schema,
654
+ schemaUrl: schemaLinkUrl,
655
+ errorMessage: '',
656
+ fileMatch
657
+ });
658
+ } else {
659
+ validationInfos.push({
660
+ isValid: false,
661
+ stringValue: schema,
662
+ schemaUrl: schemaLinkUrl,
663
+ errorMessage: schemaNotFound(),
664
+ fileMatch
665
+ });
666
+ }
667
+ } else {
668
+ validationInfos.push({
669
+ isValid: true,
670
+ stringValue: schema,
671
+ schemaUrl: schemaLinkUrl,
672
+ errorMessage: '',
673
+ fileMatch
674
+ });
675
+ }
676
+ }
677
+ return validationInfos;
678
+ };
679
+
680
+ const getJsonValidationTableEntry = validationInfo => {
514
681
  const {
682
+ isValid,
683
+ errorMessage,
684
+ schemaUrl,
685
+ stringValue,
515
686
  fileMatch
516
- } = validation;
517
- const schema = validation.schema ?? validation.url;
687
+ } = validationInfo;
688
+ if (!isValid && schemaUrl) {
689
+ return [{
690
+ type: Code,
691
+ value: fileMatch
692
+ }, {
693
+ type: Link,
694
+ value: stringValue,
695
+ href: schemaUrl,
696
+ className: TableCellInvalid,
697
+ title: errorMessage
698
+ }];
699
+ }
700
+ if (!isValid) {
701
+ return [{
702
+ type: Text,
703
+ value: fileMatch
704
+ }, {
705
+ type: Text,
706
+ value: stringValue,
707
+ className: TableCellInvalid,
708
+ title: errorMessage
709
+ }];
710
+ }
711
+ if (schemaUrl) {
712
+ return [{
713
+ type: Code,
714
+ value: fileMatch
715
+ }, {
716
+ type: Link,
717
+ value: stringValue,
718
+ href: schemaUrl
719
+ }];
720
+ }
518
721
  return [{
519
722
  type: Code,
520
723
  value: fileMatch
521
724
  }, {
522
- type: Code,
523
- value: schema
725
+ type: Text,
726
+ value: stringValue
524
727
  }];
525
728
  };
526
729
 
527
730
  const getJsonValidationDetails = async extension => {
528
731
  const validations = extension.jsonValidation || [];
529
- const rows = validations.map(getJsonValidationTableEntry);
732
+ const extensionUri = extension.uri || '';
733
+ const validationInfos = await getJsonValidationInfos(extensionUri, validations);
734
+ const rows = validationInfos.map(getJsonValidationTableEntry);
530
735
  return {
531
736
  jsonValidation: rows
532
737
  };
@@ -2396,6 +2601,7 @@ const HandleImageContextMenu = 11;
2396
2601
  const HandleReadmeContextMenu = 12;
2397
2602
  const HandleReadmeScroll = 13;
2398
2603
  const HandleTabsClick = 14;
2604
+ const HandleAdditionalDetailContextMenu = 15;
2399
2605
 
2400
2606
  const ActivationEvents = 'ActivationEvents';
2401
2607
  const Changelog = 'Changelog';
@@ -2487,12 +2693,13 @@ const getThemeMarkdown = (themes, iconThemes, productIconThemes) => {
2487
2693
  const supportsNormalCacheKey = locationProtocol => {
2488
2694
  return locationProtocol === 'http:' || locationProtocol === 'https:';
2489
2695
  };
2696
+
2490
2697
  const getMarkdownCacheKey = (hash, locationProtocol) => {
2491
2698
  if (supportsNormalCacheKey(locationProtocol)) {
2492
2699
  return `/markdown/${hash}`;
2493
2700
  }
2494
2701
  // workaround for electron bug
2495
- return `https://markdown/${hash}`;
2702
+ return `https://-/markdown/${hash}`;
2496
2703
  };
2497
2704
 
2498
2705
  const hash = async content => {
@@ -3134,6 +3341,10 @@ const getMenus = () => {
3134
3341
  }];
3135
3342
  };
3136
3343
 
3344
+ const handleAdditionalDetailContextMenu = state => {
3345
+ return state;
3346
+ };
3347
+
3137
3348
  const openExtensionSearch = async searchValue => {
3138
3349
  await openExtensionSearch$1();
3139
3350
  await setExtensionsSearchValue(searchValue);
@@ -4302,7 +4513,7 @@ const getMoreInfoEntryKeyVirtualDom = item => {
4302
4513
  return [parentNode, text(key)];
4303
4514
  };
4304
4515
 
4305
- const classLink = mergeClassNames(MoreInfoEntryValue, Link);
4516
+ const classLink = mergeClassNames(MoreInfoEntryValue, Link$1);
4306
4517
  const classCode = mergeClassNames(MoreInfoEntryValue, Code$1);
4307
4518
  const getMoreInfoEntryValueClassName = (onClick, code) => {
4308
4519
  if (onClick) {
@@ -4324,13 +4535,15 @@ const getMoreInfoEntryValueTag = (onClick, code) => {
4324
4535
  return Dd;
4325
4536
  };
4326
4537
 
4327
- const getExtraProps = title => {
4538
+ const getExtraProps = (title, onClick) => {
4539
+ const props = Object.create(null);
4328
4540
  if (title) {
4329
- return {
4330
- title
4331
- };
4541
+ props.title = title;
4332
4542
  }
4333
- return {};
4543
+ if (onClick) {
4544
+ props.tabIndex = 0;
4545
+ }
4546
+ return props;
4334
4547
  };
4335
4548
  const getMoreInfoEntryValueVirtualDom = item => {
4336
4549
  const {
@@ -4341,7 +4554,7 @@ const getMoreInfoEntryValueVirtualDom = item => {
4341
4554
  } = item;
4342
4555
  const type = getMoreInfoEntryValueTag(onClick, code);
4343
4556
  const className = getMoreInfoEntryValueClassName(onClick, code);
4344
- const extraProps = getExtraProps(title);
4557
+ const extraProps = getExtraProps(title, onClick);
4345
4558
  return [{
4346
4559
  type,
4347
4560
  className,
@@ -4410,7 +4623,8 @@ const getAdditionalDetailsVirtualDom = (showAdditionalDetails, firstHeading, ent
4410
4623
  type: Div,
4411
4624
  className: AdditionalDetails,
4412
4625
  tabIndex: 0,
4413
- childCount: 4
4626
+ childCount: 4,
4627
+ onClick: HandleAdditionalDetailContextMenu
4414
4628
  }, ...getAdditionalDetailsEntryVirtualDom(firstHeading, entries, getMoreInfoVirtualDom), ...getAdditionalDetailsEntryVirtualDom(secondHeading, secondEntries, getMoreInfoVirtualDom), ...getAdditionalDetailsEntryVirtualDom(thirdHeading, categories, getCategoriesDom), ...getAdditionalDetailsEntryVirtualDom(fourthHeading, resources, getResourcesVirtualDom)];
4415
4629
  };
4416
4630
 
@@ -4756,6 +4970,10 @@ const render2 = (uid, diffResult) => {
4756
4970
  // @ts-nocheck
4757
4971
  const renderEventListeners = () => {
4758
4972
  return [{
4973
+ name: HandleAdditionalDetailContextMenu,
4974
+ params: ['handleAdditionalDetailsContextMenu'],
4975
+ preventDefault: true
4976
+ }, {
4759
4977
  name: HandleClickCategory,
4760
4978
  params: ['handleClickCategory', TargetName]
4761
4979
  }, {
@@ -4839,6 +5057,7 @@ const commandMap = {
4839
5057
  'ExtensionDetail.getMenuEntries': getMenuEntries,
4840
5058
  'ExtensionDetail.getMenus': getMenus,
4841
5059
  'ExtensionDetail.handleClickCategory': wrapCommand(handleClickCategory),
5060
+ 'ExtensionDetail.handleAdditionalDetailsContextMenu': wrapCommand(handleAdditionalDetailContextMenu),
4842
5061
  'ExtensionDetail.handleClickDisable': wrapCommand(handleClickDisable),
4843
5062
  'ExtensionDetail.handleClickEnable': wrapCommand(handleClickEnable),
4844
5063
  'ExtensionDetail.handleClickScrollToTop': wrapCommand(handleClickScrollToTop),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lvce-editor/extension-detail-view",
3
- "version": "4.5.0",
3
+ "version": "4.7.0",
4
4
  "description": "Extension Detail View Worker",
5
5
  "repository": {
6
6
  "type": "git",