Project

General

Profile

Defect #43488 ยป redmine-core-jstoolbar-undo.patch

Jonathan Cormier, 2026-08-18 17:40

View differences:

app/assets/javascripts/jstoolbar/jstoolbar.js
307 307
    this.encloseSelection(stag,etag);
308 308
  },
309 309

  
310
  // Replaces the [start, end) range of the textarea with subst, keeping the
311
  // edit on the browser's native undo stack. Assigning textarea.value (or
312
  // using setRangeText) wipes the undo history and fires no input event, so
313
  // Ctrl+Z after a toolbar button loses everything typed before it. Returns
314
  // false when execCommand is unavailable, so callers can fall back to the
315
  // value assignment.
316
  replaceRange: function(start, end, subst) {
317
    if (typeof(document.execCommand) != 'function') { return false; }
318
    this.textarea.setSelectionRange(start, end);
319
    if (subst.length > 0) {
320
      return document.execCommand('insertText', false, subst);
321
    } else if (start != end) {
322
      return document.execCommand('delete');
323
    }
324
    return true;
325
  },
326

  
310 327
  encloseLineSelection: function(prefix, suffix, fn) {
311 328
    this.textarea.focus();
312 329

  
......
348 365
      range.move('character', -suffix.length);
349 366
      range.select();
350 367
    } else if (typeof(this.textarea["setSelectionRange"]) != "undefined") {
351
      this.textarea.value = this.textarea.value.substring(0, start) + subst +
352
      this.textarea.value.substring(end);
353
      if (sel || (!prefix && start === end)) {
354
        this.textarea.setSelectionRange(start + subst.length, start + subst.length);
368
      if (this.replaceRange(start, end, subst)) {
369
        // execCommand leaves the caret after the inserted text and keeps it
370
        // visible by itself, so scrollTop is left alone here.
371
        if (!(sel || (!prefix && start === end))) {
372
          this.textarea.setSelectionRange(start + prefix.length, start + prefix.length);
373
        }
355 374
      } else {
356
        this.textarea.setSelectionRange(start + prefix.length, start + prefix.length);
375
        this.textarea.value = this.textarea.value.substring(0, start) + subst +
376
        this.textarea.value.substring(end);
377
        if (sel || (!prefix && start === end)) {
378
          this.textarea.setSelectionRange(start + subst.length, start + subst.length);
379
        } else {
380
          this.textarea.setSelectionRange(start + prefix.length, start + prefix.length);
381
        }
382
        this.textarea.scrollTop = scrollPos;
357 383
      }
358
      this.textarea.scrollTop = scrollPos;
359 384
    }
360 385
  },
361 386

  
......
401 426
      range.select();
402 427
//      this.textarea.caretPos -= suffix.length;
403 428
    } else if (typeof(this.textarea["setSelectionRange"]) != "undefined") {
404
      this.textarea.value = this.textarea.value.substring(0, start) + subst +
405
      this.textarea.value.substring(end);
406
      if (sel) {
407
        this.textarea.setSelectionRange(start + subst.length, start + subst.length);
429
      if (this.replaceRange(start, end, subst)) {
430
        if (!sel) {
431
          this.textarea.setSelectionRange(start + prefix.length, start + prefix.length);
432
        }
408 433
      } else {
409
        this.textarea.setSelectionRange(start + prefix.length, start + prefix.length);
434
        this.textarea.value = this.textarea.value.substring(0, start) + subst +
435
        this.textarea.value.substring(end);
436
        if (sel) {
437
          this.textarea.setSelectionRange(start + subst.length, start + subst.length);
438
        } else {
439
          this.textarea.setSelectionRange(start + prefix.length, start + prefix.length);
440
        }
441
        this.textarea.scrollTop = scrollPos;
410 442
      }
411
      this.textarea.scrollTop = scrollPos;
412 443
    }
413 444
  },
414 445
  showPreview: function(event) {
    (1-1/1)