Skip to content
This repository was archived by the owner on Jul 22, 2025. It is now read-only.

Commit 122ec65

Browse files
committed
FIX: apply diffs more consistently
1. Do escaping direct in diff streamer, that way HTML tags and other unsafe chars can be displayed and fixed 2. Add safeguard to ensure streaming always stops when it was terminated elsewhere
1 parent cead887 commit 122ec65

File tree

2 files changed

+27
-10
lines changed

2 files changed

+27
-10
lines changed

assets/javascripts/discourse/components/modal/diff-modal.gjs

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,8 @@ export default class ModalDiffModal extends Component {
2424

2525
@tracked loading = false;
2626
@tracked finalResult = "";
27-
@tracked selectedText = escapeExpression(this.args.model.selectedText);
28-
@tracked diffStreamer = new DiffStreamer(this.selectedText);
27+
@tracked escapedSelectedText = escapeExpression(this.args.model.selectedText);
28+
@tracked diffStreamer = new DiffStreamer(this.args.model.selectedText);
2929
@tracked suggestion = "";
3030
@tracked
3131
smoothStreamer = new SmoothStreamer(
@@ -45,11 +45,16 @@ export default class ModalDiffModal extends Component {
4545

4646
// Prevents flash by showing the
4747
// original text when the diff is empty
48-
return this.selectedText;
48+
return this.escapedSelectedText;
4949
}
5050

5151
get isStreaming() {
52-
return this.diffStreamer.isStreaming || this.smoothStreamer.isStreaming;
52+
// diffStreamer stops "streaming" when it is finished with a chunk
53+
return (
54+
this.diffStreamer.isStreaming ||
55+
!this.diffStreamer.isDone ||
56+
this.smoothStreamer.isStreaming
57+
);
5358
}
5459

5560
get primaryBtnLabel() {
@@ -105,7 +110,7 @@ export default class ModalDiffModal extends Component {
105110
data: {
106111
location: "composer",
107112
mode: this.args.model.mode,
108-
text: this.selectedText,
113+
text: this.args.model.selectedText,
109114
custom_prompt: this.args.model.customPromptValue,
110115
force_default_locale: true,
111116
client_id: this.messageBus.clientId,
@@ -122,7 +127,7 @@ export default class ModalDiffModal extends Component {
122127

123128
if (this.suggestion) {
124129
this.args.model.toolbarEvent.replaceText(
125-
this.selectedText,
130+
this.args.model.selectedText,
126131
this.suggestion
127132
);
128133
}
@@ -131,8 +136,12 @@ export default class ModalDiffModal extends Component {
131136
this.finalResult?.length > 0
132137
? this.finalResult
133138
: this.diffStreamer.suggestion;
139+
134140
if (this.args.model.showResultAsDiff && finalResult) {
135-
this.args.model.toolbarEvent.replaceText(this.selectedText, finalResult);
141+
this.args.model.toolbarEvent.replaceText(
142+
this.args.model.selectedText,
143+
finalResult
144+
);
136145
}
137146
}
138147

assets/javascripts/discourse/lib/diff-streamer.gjs

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,9 @@ import { tracked } from "@glimmer/tracking";
22
import { cancel, later } from "@ember/runloop";
33
import loadJSDiff from "discourse/lib/load-js-diff";
44
import { parseAsync } from "discourse/lib/text";
5+
import { escapeExpression } from "discourse/lib/utilities";
56

6-
const DEFAULT_CHAR_TYPING_DELAY = 30;
7+
const DEFAULT_CHAR_TYPING_DELAY = 10;
78
const STREAMING_DIFF_TRUNCATE_THRESHOLD = 0.1;
89
const STREAMING_DIFF_TRUNCATE_BUFFER = 10;
910

@@ -51,7 +52,7 @@ export default class DiffStreamer {
5152

5253
const originalDiff = this.jsDiff.diffWordsWithSpace(
5354
this.selectedText,
54-
this.suggestion
55+
newText
5556
);
5657
this.diff = this.#formatDiffWithTags(originalDiff, false);
5758
return;
@@ -172,6 +173,10 @@ export default class DiffStreamer {
172173
}
173174

174175
async #streamNextChar() {
176+
if (!this.isStreaming || this.isDone) {
177+
return;
178+
}
179+
175180
if (this.currentWordIndex < this.words.length) {
176181
const currentToken = this.words[this.currentWordIndex];
177182

@@ -252,6 +257,7 @@ export default class DiffStreamer {
252257
return `<span>${text}</span>`;
253258
}
254259

260+
// returns an HTML safe diff (escaping all internals)
255261
#formatDiffWithTags(diffArray, highlightLastWord = true) {
256262
const wordsWithType = [];
257263
const output = [];
@@ -280,7 +286,8 @@ export default class DiffStreamer {
280286
}
281287

282288
for (let i = 0; i <= lastWordIndex; i++) {
283-
const { text, type } = wordsWithType[i];
289+
let { text, type } = wordsWithType[i];
290+
text = escapeExpression(text);
284291

285292
if (/^\s+$/.test(text)) {
286293
output.push(text);
@@ -310,6 +317,7 @@ export default class DiffStreamer {
310317
i++;
311318
}
312319

320+
chunkText = escapeExpression(chunkText);
313321
output.push(this.#wrapChunk(chunkText, chunkType));
314322
}
315323
}

0 commit comments

Comments
 (0)