markstream-angular 0.0.7 → 0.0.9-beta.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.
@@ -3,8 +3,8 @@ import { CommonModule } from '@angular/common';
3
3
  import * as i0 from '@angular/core';
4
4
  import { Input, ChangeDetectionStrategy, Component, forwardRef, inject, ChangeDetectorRef, ViewChild, ViewContainerRef, ElementRef, Renderer2, Directive, HostListener, Injectable, InjectionToken, EventEmitter, Output } from '@angular/core';
5
5
  import { normalizeCustomHtmlTags, normalizeCustomHtmlTagName, parseMarkdownToStructure, mergeCustomHtmlTags, getMarkdown, sanitizeHtmlAttrs, VOID_HTML_TAGS, BLOCKED_HTML_TAGS, isHtmlTagHardBlocked, isHtmlTagBlocked, isUnsafeHtmlUrl, shouldOpenLinkInNewTab, sanitizeImageSrc, NON_STRUCTURING_HTML_TAGS, toSafeMermaidSvgMarkup, getHtmlTagFromContent, stripCustomHtmlWrapper, STANDARD_HTML_TAGS, hasCompleteHtmlTagContent } from 'stream-markdown-parser';
6
- import { resolveStreamingTextState, createSmoothMarkdownStream } from 'markstream-core';
7
6
  import { computePosition, offset, flip, shift, autoUpdate } from '@floating-ui/dom';
7
+ import { createSmoothMarkdownStream } from 'markstream-core';
8
8
 
9
9
  const TAG_TOKEN_RE = /<\/?([A-Z][\w:-]*)(?:\s[^<>]*)?>/gi;
10
10
  function hydrateCustomTagContent(nodes, source, customHtmlTags) {
@@ -4547,35 +4547,82 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.26", ngImpo
4547
4547
  args: ['window:keydown', ['$event']]
4548
4548
  }] } });
4549
4549
 
4550
+ function settleAndMergeSegments$1(segments, segmentId) {
4551
+ return segments.reduce((result, segment) => {
4552
+ const nextSegment = segment.id === segmentId
4553
+ ? { ...segment, fading: false }
4554
+ : segment;
4555
+ const previousSegment = result[result.length - 1];
4556
+ if (previousSegment && !previousSegment.fading && !nextSegment.fading) {
4557
+ result[result.length - 1] = {
4558
+ ...previousSegment,
4559
+ content: previousSegment.content + nextSegment.content,
4560
+ };
4561
+ }
4562
+ else {
4563
+ result.push(nextSegment);
4564
+ }
4565
+ return result;
4566
+ }, []);
4567
+ }
4550
4568
  class InlineCodeNodeComponent {
4551
4569
  constructor() {
4552
- this.settledCode = '';
4553
- this.streamedDelta = '';
4554
- this.streamFadeVersion = 0;
4570
+ this.segments = [];
4571
+ this.renderedCode = '';
4572
+ this.nextSegmentId = 0;
4555
4573
  }
4556
4574
  ngOnChanges() {
4557
4575
  const nextCode = getString$1(this.node?.code);
4558
4576
  const streamStateKey = getString$1(this.indexKey).trim();
4559
4577
  const textStreamState = this.context?.textStreamState;
4560
- const rendered = `${this.settledCode}${this.streamedDelta}`;
4561
4578
  const previousPersisted = streamStateKey
4562
4579
  ? textStreamState?.get(streamStateKey)
4563
4580
  : undefined;
4564
- const previousCode = previousPersisted ?? rendered;
4565
- const nextState = resolveStreamingTextState({
4566
- nextContent: nextCode,
4567
- previousContent: previousCode,
4568
- typewriterEnabled: this.fadeEnabled,
4569
- });
4570
- this.settledCode = nextState.settledContent;
4571
- this.streamedDelta = nextState.streamedDelta;
4572
- if (nextState.appended)
4573
- this.streamFadeVersion += 1;
4581
+ const previousCode = previousPersisted ?? this.renderedCode;
4582
+ const resumeFromPersistedCode = previousPersisted !== undefined
4583
+ && previousPersisted !== this.renderedCode;
4584
+ if (!this.fadeEnabled) {
4585
+ this.setFullCode(nextCode);
4586
+ }
4587
+ else if (nextCode !== previousCode) {
4588
+ if (previousCode && nextCode.startsWith(previousCode)) {
4589
+ const appendedCode = nextCode.slice(previousCode.length);
4590
+ const lastSegment = this.segments[this.segments.length - 1];
4591
+ if (resumeFromPersistedCode) {
4592
+ this.segments = [
4593
+ { id: this.nextSegmentId++, content: previousCode, fading: false },
4594
+ { id: this.nextSegmentId++, content: appendedCode, fading: true },
4595
+ ];
4596
+ }
4597
+ else if (lastSegment?.fading) {
4598
+ this.segments = [
4599
+ ...this.segments.slice(0, -1),
4600
+ { ...lastSegment, content: lastSegment.content + appendedCode },
4601
+ ];
4602
+ }
4603
+ else {
4604
+ this.segments = [
4605
+ ...this.segments,
4606
+ { id: this.nextSegmentId++, content: appendedCode, fading: true },
4607
+ ];
4608
+ }
4609
+ }
4610
+ else {
4611
+ this.setFullCode(nextCode);
4612
+ }
4613
+ }
4614
+ this.renderedCode = nextCode;
4574
4615
  if (streamStateKey)
4575
4616
  textStreamState?.set(streamStateKey, nextCode);
4576
4617
  }
4577
- get streamedDeltaClass() {
4578
- return this.streamFadeVersion % 2 === 0
4618
+ get settledCode() {
4619
+ return this.segments.filter(segment => !segment.fading).map(segment => segment.content).join('');
4620
+ }
4621
+ get streamedDelta() {
4622
+ return this.segments.filter(segment => segment.fading).map(segment => segment.content).join('');
4623
+ }
4624
+ streamedDeltaClass(segment) {
4625
+ return segment.id % 2 === 0
4579
4626
  ? 'markstream-angular-text__stream-delta--a'
4580
4627
  : 'markstream-angular-text__stream-delta--b';
4581
4628
  }
@@ -4593,23 +4640,31 @@ class InlineCodeNodeComponent {
4593
4640
  return this.context.typewriter;
4594
4641
  return false;
4595
4642
  }
4596
- settleStreamedDelta() {
4597
- if (!this.streamedDelta)
4643
+ trackSegment(_index, segment) {
4644
+ return segment.id;
4645
+ }
4646
+ settleSegment(segmentId) {
4647
+ if (!this.segments.some(segment => segment.id === segmentId && segment.fading))
4598
4648
  return;
4599
- this.settledCode = `${this.settledCode}${this.streamedDelta}`;
4600
- this.streamedDelta = '';
4649
+ this.segments = settleAndMergeSegments$1(this.segments, segmentId);
4650
+ }
4651
+ setFullCode(content) {
4652
+ if (this.segments.length === 1 && !this.segments[0]?.fading && this.segments[0].content === content)
4653
+ return;
4654
+ this.segments = content
4655
+ ? [{ id: this.nextSegmentId++, content, fading: false }]
4656
+ : [];
4601
4657
  }
4602
4658
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.26", ngImport: i0, type: InlineCodeNodeComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
4603
4659
  static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.26", type: InlineCodeNodeComponent, isStandalone: true, selector: "markstream-angular-inline-code-node", inputs: { node: "node", context: "context", indexKey: "indexKey", typewriter: "typewriter", fade: "fade" }, usesOnChanges: true, ngImport: i0, template: `
4604
4660
  <code>
4605
- <span *ngIf="settledCode">{{ settledCode }}</span>
4606
4661
  <span
4607
- *ngIf="streamedDelta"
4608
- [class]="'markstream-angular-text__stream-delta ' + streamedDeltaClass"
4609
- (animationend)="settleStreamedDelta()"
4610
- >{{ streamedDelta }}</span>
4662
+ *ngFor="let segment of segments; trackBy: trackSegment"
4663
+ [class]="segment.fading ? 'markstream-angular-text__stream-delta ' + streamedDeltaClass(segment) : ''"
4664
+ (animationend)="segment.fading && settleSegment(segment.id)"
4665
+ >{{ segment.content }}</span>
4611
4666
  </code>
4612
- `, isInline: true, dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
4667
+ `, isInline: true, dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
4613
4668
  }
4614
4669
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.26", ngImport: i0, type: InlineCodeNodeComponent, decorators: [{
4615
4670
  type: Component,
@@ -4619,12 +4674,11 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.26", ngImpo
4619
4674
  imports: [CommonModule],
4620
4675
  template: `
4621
4676
  <code>
4622
- <span *ngIf="settledCode">{{ settledCode }}</span>
4623
4677
  <span
4624
- *ngIf="streamedDelta"
4625
- [class]="'markstream-angular-text__stream-delta ' + streamedDeltaClass"
4626
- (animationend)="settleStreamedDelta()"
4627
- >{{ streamedDelta }}</span>
4678
+ *ngFor="let segment of segments; trackBy: trackSegment"
4679
+ [class]="segment.fading ? 'markstream-angular-text__stream-delta ' + streamedDeltaClass(segment) : ''"
4680
+ (animationend)="segment.fading && settleSegment(segment.id)"
4681
+ >{{ segment.content }}</span>
4628
4682
  </code>
4629
4683
  `,
4630
4684
  changeDetection: ChangeDetectionStrategy.OnPush,
@@ -4810,11 +4864,29 @@ function hideTooltip(immediate = false) {
4810
4864
  hideTimer = setTimeout(doHide, 120);
4811
4865
  }
4812
4866
 
4867
+ function settleAndMergeSegments(segments, segmentId) {
4868
+ return segments.reduce((result, segment) => {
4869
+ const nextSegment = segmentId == null || segment.id === segmentId
4870
+ ? { ...segment, fading: false }
4871
+ : segment;
4872
+ const previousSegment = result[result.length - 1];
4873
+ if (previousSegment && !previousSegment.fading && !nextSegment.fading) {
4874
+ result[result.length - 1] = {
4875
+ ...previousSegment,
4876
+ content: previousSegment.content + nextSegment.content,
4877
+ };
4878
+ }
4879
+ else {
4880
+ result.push(nextSegment);
4881
+ }
4882
+ return result;
4883
+ }, []);
4884
+ }
4813
4885
  class TextNodeComponent {
4814
4886
  constructor() {
4815
- this.settledText = '';
4816
- this.streamedDelta = '';
4817
- this.streamFadeVersion = 0;
4887
+ this.segments = [];
4888
+ this.renderedText = '';
4889
+ this.nextSegmentId = 0;
4818
4890
  }
4819
4891
  ngOnChanges() {
4820
4892
  const nextText = getString$1(this.node?.content);
@@ -4822,41 +4894,59 @@ class TextNodeComponent {
4822
4894
  const textStreamState = this.context?.textStreamState;
4823
4895
  const streamRenderVersion = this.context?.streamRenderVersion;
4824
4896
  const streamRenderVersionChanged = streamRenderVersion !== this.lastStreamRenderVersion;
4825
- const rendered = `${this.settledText}${this.streamedDelta}`;
4826
4897
  const previousPersisted = streamStateKey
4827
4898
  ? textStreamState?.get(streamStateKey)
4828
4899
  : undefined;
4829
- const previousText = previousPersisted ?? rendered;
4900
+ const previousText = previousPersisted ?? this.renderedText;
4901
+ const resumeFromPersistedText = previousPersisted !== undefined
4902
+ && previousPersisted !== this.renderedText;
4830
4903
  if (!this.fadeEnabled) {
4831
- this.settledText = nextText;
4832
- this.streamedDelta = '';
4904
+ this.setFullText(nextText);
4905
+ this.renderedText = nextText;
4833
4906
  if (streamStateKey)
4834
4907
  textStreamState?.set(streamStateKey, nextText);
4835
4908
  this.lastStreamRenderVersion = streamRenderVersion;
4836
4909
  return;
4837
4910
  }
4838
4911
  if (nextText === previousText) {
4839
- if (this.streamedDelta && streamRenderVersionChanged) {
4840
- this.settleStreamedDelta();
4912
+ if (this.segments.some(segment => segment.fading) && streamRenderVersionChanged) {
4913
+ this.settleFadingSegments();
4841
4914
  }
4842
- else if (rendered !== nextText) {
4843
- this.settledText = nextText;
4844
- this.streamedDelta = '';
4915
+ else if (this.renderedText !== nextText) {
4916
+ this.setFullText(nextText);
4845
4917
  }
4918
+ this.renderedText = nextText;
4846
4919
  if (streamStateKey)
4847
4920
  textStreamState?.set(streamStateKey, nextText);
4848
4921
  this.lastStreamRenderVersion = streamRenderVersion;
4849
4922
  return;
4850
4923
  }
4851
- const nextState = resolveStreamingTextState({
4852
- nextContent: nextText,
4853
- previousContent: previousText,
4854
- typewriterEnabled: this.fadeEnabled,
4855
- });
4856
- this.settledText = nextState.settledContent;
4857
- this.streamedDelta = nextState.streamedDelta;
4858
- if (nextState.appended)
4859
- this.streamFadeVersion += 1;
4924
+ if (previousText && nextText.startsWith(previousText)) {
4925
+ const appendedText = nextText.slice(previousText.length);
4926
+ const lastSegment = this.segments[this.segments.length - 1];
4927
+ if (resumeFromPersistedText) {
4928
+ this.segments = [
4929
+ { id: this.nextSegmentId++, content: previousText, fading: false },
4930
+ { id: this.nextSegmentId++, content: appendedText, fading: true },
4931
+ ];
4932
+ }
4933
+ else if (lastSegment?.fading) {
4934
+ this.segments = [
4935
+ ...this.segments.slice(0, -1),
4936
+ { ...lastSegment, content: lastSegment.content + appendedText },
4937
+ ];
4938
+ }
4939
+ else {
4940
+ this.segments = [
4941
+ ...this.segments,
4942
+ { id: this.nextSegmentId++, content: appendedText, fading: true },
4943
+ ];
4944
+ }
4945
+ }
4946
+ else {
4947
+ this.setFullText(nextText);
4948
+ }
4949
+ this.renderedText = nextText;
4860
4950
  if (streamStateKey)
4861
4951
  textStreamState?.set(streamStateKey, nextText);
4862
4952
  this.lastStreamRenderVersion = streamRenderVersion;
@@ -4864,8 +4954,14 @@ class TextNodeComponent {
4864
4954
  get centered() {
4865
4955
  return !!this.node?.center;
4866
4956
  }
4867
- get streamedDeltaClass() {
4868
- return this.streamFadeVersion % 2 === 0
4957
+ get settledText() {
4958
+ return this.segments.filter(segment => !segment.fading).map(segment => segment.content).join('');
4959
+ }
4960
+ get streamedDelta() {
4961
+ return this.segments.filter(segment => segment.fading).map(segment => segment.content).join('');
4962
+ }
4963
+ streamedDeltaClass(segment) {
4964
+ return segment.id % 2 === 0
4869
4965
  ? 'markstream-angular-text__stream-delta--a'
4870
4966
  : 'markstream-angular-text__stream-delta--b';
4871
4967
  }
@@ -4883,35 +4979,45 @@ class TextNodeComponent {
4883
4979
  return this.context.typewriter;
4884
4980
  return false;
4885
4981
  }
4886
- settleStreamedDelta() {
4887
- if (!this.streamedDelta)
4982
+ trackSegment(_index, segment) {
4983
+ return segment.id;
4984
+ }
4985
+ settleSegment(segmentId) {
4986
+ if (!this.segments.some(segment => segment.id === segmentId && segment.fading))
4987
+ return;
4988
+ this.segments = settleAndMergeSegments(this.segments, segmentId);
4989
+ }
4990
+ setFullText(content) {
4991
+ if (this.segments.length === 1 && !this.segments[0]?.fading && this.segments[0].content === content)
4888
4992
  return;
4889
- this.settledText = `${this.settledText}${this.streamedDelta}`;
4890
- this.streamedDelta = '';
4993
+ this.segments = content
4994
+ ? [{ id: this.nextSegmentId++, content, fading: false }]
4995
+ : [];
4996
+ }
4997
+ settleFadingSegments() {
4998
+ this.segments = settleAndMergeSegments(this.segments);
4891
4999
  }
4892
5000
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.26", ngImport: i0, type: TextNodeComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
4893
5001
  static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.26", type: TextNodeComponent, isStandalone: true, selector: "markstream-angular-text-node", inputs: { node: "node", context: "context", indexKey: "indexKey", typewriter: "typewriter", fade: "fade" }, usesOnChanges: true, ngImport: i0, template: `
4894
5002
  <ng-container *ngIf="!centered; else centeredText">
4895
5003
  <span class="markstream-angular-text-node">
4896
- <span *ngIf="settledText">{{ settledText }}</span>
4897
5004
  <span
4898
- *ngIf="streamedDelta"
4899
- [class]="'markstream-angular-text__stream-delta ' + streamedDeltaClass"
4900
- (animationend)="settleStreamedDelta()"
4901
- >{{ streamedDelta }}</span>
5005
+ *ngFor="let segment of segments; trackBy: trackSegment"
5006
+ [class]="segment.fading ? 'markstream-angular-text__stream-delta ' + streamedDeltaClass(segment) : ''"
5007
+ (animationend)="segment.fading && settleSegment(segment.id)"
5008
+ >{{ segment.content }}</span>
4902
5009
  </span>
4903
5010
  </ng-container>
4904
5011
  <ng-template #centeredText>
4905
5012
  <span class="markstream-angular-text-node markstream-angular-text--centered">
4906
- <span *ngIf="settledText">{{ settledText }}</span>
4907
5013
  <span
4908
- *ngIf="streamedDelta"
4909
- [class]="'markstream-angular-text__stream-delta ' + streamedDeltaClass"
4910
- (animationend)="settleStreamedDelta()"
4911
- >{{ streamedDelta }}</span>
5014
+ *ngFor="let segment of segments; trackBy: trackSegment"
5015
+ [class]="segment.fading ? 'markstream-angular-text__stream-delta ' + streamedDeltaClass(segment) : ''"
5016
+ (animationend)="segment.fading && settleSegment(segment.id)"
5017
+ >{{ segment.content }}</span>
4912
5018
  </span>
4913
5019
  </ng-template>
4914
- `, isInline: true, dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
5020
+ `, isInline: true, dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
4915
5021
  }
4916
5022
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.26", ngImport: i0, type: TextNodeComponent, decorators: [{
4917
5023
  type: Component,
@@ -4922,22 +5028,20 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.26", ngImpo
4922
5028
  template: `
4923
5029
  <ng-container *ngIf="!centered; else centeredText">
4924
5030
  <span class="markstream-angular-text-node">
4925
- <span *ngIf="settledText">{{ settledText }}</span>
4926
5031
  <span
4927
- *ngIf="streamedDelta"
4928
- [class]="'markstream-angular-text__stream-delta ' + streamedDeltaClass"
4929
- (animationend)="settleStreamedDelta()"
4930
- >{{ streamedDelta }}</span>
5032
+ *ngFor="let segment of segments; trackBy: trackSegment"
5033
+ [class]="segment.fading ? 'markstream-angular-text__stream-delta ' + streamedDeltaClass(segment) : ''"
5034
+ (animationend)="segment.fading && settleSegment(segment.id)"
5035
+ >{{ segment.content }}</span>
4931
5036
  </span>
4932
5037
  </ng-container>
4933
5038
  <ng-template #centeredText>
4934
5039
  <span class="markstream-angular-text-node markstream-angular-text--centered">
4935
- <span *ngIf="settledText">{{ settledText }}</span>
4936
5040
  <span
4937
- *ngIf="streamedDelta"
4938
- [class]="'markstream-angular-text__stream-delta ' + streamedDeltaClass"
4939
- (animationend)="settleStreamedDelta()"
4940
- >{{ streamedDelta }}</span>
5041
+ *ngFor="let segment of segments; trackBy: trackSegment"
5042
+ [class]="segment.fading ? 'markstream-angular-text__stream-delta ' + streamedDeltaClass(segment) : ''"
5043
+ (animationend)="segment.fading && settleSegment(segment.id)"
5044
+ >{{ segment.content }}</span>
4941
5045
  </span>
4942
5046
  </ng-template>
4943
5047
  `,