@design.estate/dees-catalog 13.1.0 → 13.2.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.
@@ -27,12 +27,15 @@ import {
27
27
  computeLineDiff,
28
28
  diffStats,
29
29
  foldContextRows,
30
- parseUnifiedDiff,
30
+ parseUnifiedDiffDocument,
31
31
  toUnifiedDiff,
32
32
  type IDiffRow,
33
33
  type IDiffSegment,
34
34
  type ISplitCell,
35
35
  type TDiffDisplayRow,
36
+ type IUnifiedDiffDocument,
37
+ type IUnifiedDiffFileSection,
38
+ type IUnifiedDiffMetadata,
36
39
  } from './codebox.diff.js';
37
40
 
38
41
  declare global {
@@ -79,6 +82,10 @@ export class DeesDataviewCodebox extends DeesElement {
79
82
  @property({ type: String })
80
83
  accessor unifiedDiff: string = '';
81
84
 
85
+ /** The unified patch ends before the producer's complete output. */
86
+ @property({ type: Boolean })
87
+ accessor unifiedDiffTruncated: boolean = false;
88
+
82
89
  /** '' = regular code; 'inline' = unified rows; 'split' = side by side; 'auto' splits at 640px. */
83
90
  @property({ type: String })
84
91
  accessor diffView: '' | TDiffViewRequest = '';
@@ -414,18 +421,65 @@ export class DeesDataviewCodebox extends DeesElement {
414
421
  .diffStat.removed {
415
422
  color: var(--dees-color-accent-error);
416
423
  }
424
+
425
+ .diffFileHeader {
426
+ display: flex;
427
+ align-items: baseline;
428
+ gap: var(--dees-spacing-md);
429
+ min-width: 0;
430
+ padding: 6px var(--dees-spacing-lg);
431
+ border-top: 1px solid var(--dees-color-border-subtle);
432
+ border-bottom: 1px solid var(--dees-color-border-subtle);
433
+ background: var(--dees-color-bg-secondary);
434
+ }
435
+
436
+ .diffFile:first-child > .diffFileHeader { border-top: 0; }
437
+
438
+ .diffFilePath {
439
+ min-width: 0;
440
+ overflow: hidden;
441
+ text-overflow: ellipsis;
442
+ white-space: nowrap;
443
+ color: var(--dees-color-text-primary);
444
+ font: 600 var(--dees-font-control-size-sm)/1.4 var(--dees-font-family-mono);
445
+ }
446
+
447
+ .diffFileMeta {
448
+ flex: 0 1 auto;
449
+ min-width: 0;
450
+ overflow: hidden;
451
+ text-overflow: ellipsis;
452
+ white-space: nowrap;
453
+ color: var(--dees-color-text-muted);
454
+ font-size: var(--dees-font-caption1-size, 11px);
455
+ }
456
+
457
+ .diffFileNotice,
458
+ .diffDocumentNotice {
459
+ padding: var(--dees-spacing-lg);
460
+ color: var(--dees-color-text-muted);
461
+ font-size: var(--dees-font-control-size-sm);
462
+ }
463
+
464
+ .diffQualifier {
465
+ margin-left: var(--dees-spacing-sm);
466
+ color: var(--dees-color-text-muted);
467
+ font-weight: 500;
468
+ }
417
469
  `,
418
470
  ];
419
471
 
420
472
  render(): TemplateResult {
421
473
  const contextMenuOptions = [
422
- {
423
- name: 'Copy Code',
424
- iconName: 'lucide:Copy',
425
- action: async () => {
426
- await this.copyCode();
427
- },
428
- },
474
+ ...(!this.unifiedDiff
475
+ ? [{
476
+ name: 'Copy Code',
477
+ iconName: 'lucide:Copy',
478
+ action: async () => {
479
+ await this.copyCode();
480
+ },
481
+ }]
482
+ : []),
429
483
  ...(this.isDiffActive
430
484
  ? [{
431
485
  name: 'Copy Unified Diff',
@@ -454,13 +508,15 @@ export class DeesDataviewCodebox extends DeesElement {
454
508
  @option-activate=${(event: CustomEvent) => { event.stopPropagation(); this.setDiffView(event.detail.value); }}></dees-input-multitoggle>
455
509
  `
456
510
  : ''}
457
- <div
458
- class="copyButton ${this.copied ? 'copied' : ''}"
459
- title="Copy code"
460
- @click=${this.copyCode}
461
- >
462
- <dees-icon .icon=${this.copied ? 'lucide:Check' : 'lucide:Copy'} iconSize="14"></dees-icon>
463
- </div>
511
+ ${!this.unifiedDiff ? html`
512
+ <div
513
+ class="copyButton ${this.copied ? 'copied' : ''}"
514
+ title="Copy code"
515
+ @click=${this.copyCode}
516
+ >
517
+ <dees-icon .icon=${this.copied ? 'lucide:Check' : 'lucide:Copy'} iconSize="14"></dees-icon>
518
+ </div>
519
+ ` : ''}
464
520
  </div>
465
521
  </div>
466
522
  ${this.isDiffActive ? this.renderDiff() : this.renderCode()}
@@ -492,20 +548,85 @@ export class DeesDataviewCodebox extends DeesElement {
492
548
  }
493
549
 
494
550
  private renderDiffFooter(): TemplateResult {
495
- const stats = diffStats(this.diffRows);
551
+ const summary = this.diffDocument?.summary;
552
+ const stats = summary
553
+ ? summary.type === 'unparseable' ? undefined : summary.stats
554
+ : diffStats(this.diffRows);
555
+ const qualifiers: string[] = [];
556
+ if (summary?.type === 'partial') qualifiers.push('Partial diff');
557
+ if (summary?.type === 'unparseable') qualifiers.push('Diff format unavailable');
558
+ if (summary?.binaryFiles) {
559
+ qualifiers.push(`${summary.binaryFiles} binary ${summary.binaryFiles === 1 ? 'file' : 'files'}`);
560
+ }
561
+ if (summary?.type === 'exact' && !stats && !summary.binaryFiles) qualifiers.push('Metadata-only diff');
496
562
  return html`
497
563
  <div class="lineCount">
498
- <span class="diffStat added">+${stats.added}</span>
499
- <span class="diffStat removed">−${stats.removed}</span>
564
+ ${stats ? html`
565
+ <span class="diffStat added">+${stats.added}</span>
566
+ <span class="diffStat removed">−${stats.removed}</span>
567
+ ` : ''}
568
+ ${qualifiers.length ? html`<span class="diffQualifier">${qualifiers.join(' · ')}</span>` : ''}
500
569
  </div>
501
570
  `;
502
571
  }
503
572
 
504
573
  private renderDiff(): TemplateResult {
505
- const displayRows = this.displayRowsWithFolds();
574
+ const sections = this.displaySectionsWithFolds();
575
+ if (!sections.length) {
576
+ return html`<div class="diffDocumentNotice">The diff has no displayable file sections.</div>`;
577
+ }
506
578
  return this.effectiveDiffView === 'split'
507
- ? this.renderSplitGrid(displayRows)
508
- : html`<div class="diffScroll">${this.renderInlineGrid(displayRows)}</div>`;
579
+ ? html`${sections.map(({ file, rows }) => html`
580
+ <section class="diffFile">
581
+ ${this.renderDiffFileHeader(file)}
582
+ ${rows.length ? this.renderSplitGrid(rows) : this.renderDiffFileNotice(file)}
583
+ </section>
584
+ `)}`
585
+ : html`<div class="diffScroll">${sections.map(({ file, rows }) => html`
586
+ <section class="diffFile">
587
+ ${this.renderDiffFileHeader(file)}
588
+ ${rows.length ? this.renderInlineGrid(rows) : this.renderDiffFileNotice(file)}
589
+ </section>
590
+ `)}</div>`;
591
+ }
592
+
593
+ private renderDiffFileHeader(fileArg: IUnifiedDiffFileSection): TemplateResult | '' {
594
+ const details = this.diffFileMetadataLabels(fileArg);
595
+ const path = fileArg.beforePath && fileArg.afterPath && fileArg.beforePath !== fileArg.afterPath
596
+ ? `${fileArg.beforePath} → ${fileArg.afterPath}`
597
+ : fileArg.displayPath;
598
+ if (!path && !details.length) return '';
599
+ return html`
600
+ <div class="diffFileHeader">
601
+ <div class="diffFilePath">${path ?? 'Unscoped patch'}</div>
602
+ ${details.length ? html`<div class="diffFileMeta">${details.join(' · ')}</div>` : ''}
603
+ </div>
604
+ `;
605
+ }
606
+
607
+ private diffFileMetadataLabels(fileArg: IUnifiedDiffFileSection): string[] {
608
+ const labels: string[] = [];
609
+ const metadata = (kindArg: IUnifiedDiffMetadata['kind']) =>
610
+ fileArg.metadata.filter((entry) => entry.kind === kindArg).map((entry) => entry.value);
611
+ const newFileMode = metadata('new-file-mode')[0];
612
+ const deletedFileMode = metadata('deleted-file-mode')[0];
613
+ const oldMode = metadata('old-mode')[0];
614
+ const newMode = metadata('new-mode')[0];
615
+ if (newFileMode) labels.push(`New file · mode ${newFileMode}`);
616
+ if (deletedFileMode) labels.push(`Deleted file · mode ${deletedFileMode}`);
617
+ if (metadata('rename-from').length || metadata('rename-to').length) labels.push('Renamed');
618
+ if (metadata('copy-from').length || metadata('copy-to').length) labels.push('Copied');
619
+ if (oldMode || newMode) labels.push(`Mode ${oldMode ?? '?'} → ${newMode ?? '?'}`);
620
+ labels.push(...metadata('similarity-index').map((value) => `${value} similar`));
621
+ labels.push(...metadata('dissimilarity-index').map((value) => `${value} dissimilar`));
622
+ if (fileArg.binary) labels.push('Binary change');
623
+ return labels;
624
+ }
625
+
626
+ private renderDiffFileNotice(fileArg: IUnifiedDiffFileSection): TemplateResult {
627
+ return html`<div class="diffFileNotice">${fileArg.binary
628
+ ? 'Binary content is not shown.'
629
+ : 'No textual line changes.'}</div>`;
509
630
  }
510
631
 
511
632
  private renderInlineGrid(displayRows: TDiffDisplayRow[]): TemplateResult {
@@ -627,6 +748,7 @@ export class DeesDataviewCodebox extends DeesElement {
627
748
  private highlightToken = 0;
628
749
  private copiedResetTimeout: ReturnType<typeof setTimeout> | null = null;
629
750
  private diffRows: IDiffRow[] = [];
751
+ private diffDocument: IUnifiedDiffDocument | null = null;
630
752
  private diffHighlightCache = new Map<string, string>();
631
753
  private autoDiffResizeObserver: ResizeObserver | null = null;
632
754
 
@@ -687,13 +809,21 @@ export class DeesDataviewCodebox extends DeesElement {
687
809
  changedProperties.has('codeToDisplay')
688
810
  || changedProperties.has('codeBefore')
689
811
  || changedProperties.has('unifiedDiff')
812
+ || changedProperties.has('unifiedDiffTruncated')
690
813
  || changedProperties.has('diffView')
691
814
  ) {
692
815
  if (this.isDiffActive) {
693
- this.diffRows = this.unifiedDiff
694
- ? parseUnifiedDiff(this.unifiedDiff)
695
- : computeLineDiff(this.codeBefore, this.codeToDisplay);
816
+ if (this.unifiedDiff) {
817
+ this.diffDocument = parseUnifiedDiffDocument(this.unifiedDiff, {
818
+ truncated: this.unifiedDiffTruncated,
819
+ });
820
+ this.diffRows = this.diffDocument.files.flatMap((file) => file.rows);
821
+ } else {
822
+ this.diffDocument = null;
823
+ this.diffRows = computeLineDiff(this.codeBefore, this.codeToDisplay);
824
+ }
696
825
  } else {
826
+ this.diffDocument = null;
697
827
  this.diffRows = [];
698
828
  }
699
829
  this.diffHighlightCache.clear();
@@ -705,23 +835,36 @@ export class DeesDataviewCodebox extends DeesElement {
705
835
  }
706
836
  }
707
837
 
708
- private displayRowsWithFolds(): TDiffDisplayRow[] {
709
- const folded = foldContextRows(this.diffRows);
710
- const result: TDiffDisplayRow[] = [];
838
+ private displaySectionsWithFolds(): Array<{
839
+ file: IUnifiedDiffFileSection;
840
+ rows: TDiffDisplayRow[];
841
+ }> {
842
+ const files = this.diffDocument?.files ?? [{
843
+ metadata: [],
844
+ rows: this.diffRows,
845
+ binary: false,
846
+ } satisfies IUnifiedDiffFileSection];
711
847
  let foldOrdinal = 0;
712
- for (const row of folded) {
713
- if (row.kind === 'fold') {
714
- if (this.expandedFolds.has(foldOrdinal)) {
715
- result.push(...row.rows);
848
+ return files.map((file) => {
849
+ const result: TDiffDisplayRow[] = [];
850
+ for (const row of foldContextRows(file.rows)) {
851
+ if (row.kind === 'fold') {
852
+ if (this.expandedFolds.has(foldOrdinal)) {
853
+ result.push(...row.rows);
854
+ } else {
855
+ result.push({ ...row, ordinal: foldOrdinal });
856
+ }
857
+ foldOrdinal++;
716
858
  } else {
717
- result.push({ ...row, ordinal: foldOrdinal });
859
+ result.push(row);
718
860
  }
719
- foldOrdinal++;
720
- } else {
721
- result.push(row);
722
861
  }
723
- }
724
- return result;
862
+ return { file, rows: result };
863
+ });
864
+ }
865
+
866
+ private displayRowsWithFolds(): TDiffDisplayRow[] {
867
+ return this.displaySectionsWithFolds().flatMap((section) => section.rows);
725
868
  }
726
869
 
727
870
  public async updated(changedProperties: Map<string, any>) {
@@ -742,6 +885,7 @@ export class DeesDataviewCodebox extends DeesElement {
742
885
  changedProperties.has('codeToDisplay')
743
886
  || changedProperties.has('codeBefore')
744
887
  || changedProperties.has('unifiedDiff')
888
+ || changedProperties.has('unifiedDiffTruncated')
745
889
  || changedProperties.has('diffView')
746
890
  || changedProperties.has('expandedFolds')
747
891
  || changedProperties.has('progLang')
@@ -814,6 +958,9 @@ export class DeesDataviewCodebox extends DeesElement {
814
958
  }
815
959
 
816
960
  private copyCode = async () => {
961
+ if (this.unifiedDiff) {
962
+ return;
963
+ }
817
964
  // in diff mode the copy button yields the after-state text
818
965
  const textToCopy = this.isDiffActive
819
966
  ? this.diffRows