From 05d308870163a5b344341757b5418c4b77356522 Mon Sep 17 00:00:00 2001 From: ishikawa999 <14245262+ishikawa999@users.noreply.github.com> Date: Fri, 29 May 2026 12:00:38 +0900 Subject: [PATCH 2/2] Extend Tab/Shift+Tab indentation from list items to any selected text --- app/helpers/application_helper.rb | 6 +- ...ller.js => selection_indent_controller.js} | 29 +++--- ...ndent_test.rb => selection_indent_test.rb} | 97 +++++++------------ 3 files changed, 48 insertions(+), 84 deletions(-) rename app/javascript/controllers/{list_indent_controller.js => selection_indent_controller.js} (59%) rename test/system/{list_indent_test.rb => selection_indent_test.rb} (51%) diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index d7e3c7e87..35b895892 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -1433,10 +1433,10 @@ module ApplicationHelper return {} if Setting.text_formatting.blank? { - controller: 'list-autofill list-indent table-paste', - action: 'beforeinput->list-autofill#handleBeforeInput keydown.tab->list-indent#run keydown.shift+tab->list-indent#run paste->table-paste#handlePaste', + controller: 'list-autofill selection-indent table-paste', + action: 'beforeinput->list-autofill#handleBeforeInput keydown.tab->selection-indent#run keydown.shift+tab->selection-indent#run paste->table-paste#handlePaste', list_autofill_text_formatting_param: Setting.text_formatting, - list_indent_text_formatting_param: Setting.text_formatting, + selection_indent_text_formatting_param: Setting.text_formatting, table_paste_text_formatting_param: Setting.text_formatting } end diff --git a/app/javascript/controllers/list_indent_controller.js b/app/javascript/controllers/selection_indent_controller.js similarity index 59% rename from app/javascript/controllers/list_indent_controller.js rename to app/javascript/controllers/selection_indent_controller.js index 34ec88c74..e6555e7fe 100644 --- a/app/javascript/controllers/list_indent_controller.js +++ b/app/javascript/controllers/selection_indent_controller.js @@ -1,8 +1,7 @@ import { Controller } from '@hotwired/stimulus' export default class extends Controller { - #bulletPattern = /^\s*[*+\-] / - #orderedPattern = /^\s*\d+[.)] / + #spaces = 2 run(event) { const format = event.params.textFormatting @@ -17,35 +16,29 @@ export default class extends Controller { const endPos = end === -1 ? value.length : end const selectedText = value.slice(start, endPos) const lines = selectedText.split("\n") - const spaces = this.#indentSize(lines.find(l => this.#indentSize(l)) || "") - if (!spaces) return event.preventDefault() const newLines = event.shiftKey - ? lines.map(line => this.#unindentLine(line, spaces)) - : lines.map(line => this.#indentLine(line, spaces)) + ? lines.map(line => this.#unindentLine(line)) + : lines.map(line => this.#indentLine(line)) const newText = newLines.join("\n") input.setRangeText(newText, start, endPos, "preserve") + input.setSelectionRange( + Math.max(start, selectionStart + newLines[0].length - lines[0].length), + selectionEnd + newText.length - selectedText.length + ) } - #indentSize(line) { - if (this.#bulletPattern.test(line)) return 2 - if (this.#orderedPattern.test(line)) return 4 - return 0 + #indentLine(line) { + return " ".repeat(this.#spaces) + line } - #indentLine(line, spaces) { - if (!this.#indentSize(line)) return line - return " ".repeat(spaces) + line - } - - #unindentLine(line, spaces) { + #unindentLine(line) { const currentIndent = line.match(/^(\s*)/)[1].length - const remove = Math.min(spaces, currentIndent) + const remove = Math.min(this.#spaces, currentIndent) return line.slice(remove) } - } diff --git a/test/system/list_indent_test.rb b/test/system/selection_indent_test.rb similarity index 51% rename from test/system/list_indent_test.rb rename to test/system/selection_indent_test.rb index efd7b0d88..62dc9269c 100644 --- a/test/system/list_indent_test.rb +++ b/test/system/selection_indent_test.rb @@ -2,143 +2,114 @@ require_relative '../application_system_test_case' -class ListIndentSystemTest < ApplicationSystemTestCase +class SelectionIndentSystemTest < ApplicationSystemTestCase def setup super log_user('jsmith', 'jsmith') end - # Tab: inserts 2 spaces for bullet lists, 4 spaces for ordered lists - def test_tab_indents_common_mark_bullet_list + def test_tab_indents_selected_text with_settings :text_formatting => 'common_mark' do visit '/projects/ecookbook/issues/new' within('form#issue-form') do + text = "hello" el = find('#issue_description') el.click - set_textarea_value 'issue_description', "- item", selection: [0, text.length] + set_textarea_value 'issue_description', text, selection: [0, text.length] el.send_keys(:tab) - assert_equal " - item", el.value + assert_equal " hello", el.value end end end - def test_tab_indents_common_mark_ordered_list + def test_tab_indents_multiple_selected_lines with_settings :text_formatting => 'common_mark' do visit '/projects/ecookbook/issues/new' within('form#issue-form') do + text = "hello\nworld" el = find('#issue_description') el.click - set_textarea_value 'issue_description', "1. item", selection: [0, text.length] + set_textarea_value 'issue_description', text, selection: [0, text.length] el.send_keys(:tab) - assert_equal " 1. item", el.value + assert_equal " hello\n world", el.value end end end - # Shift+Tab: removes indentation - def test_shift_tab_unindents_common_mark_bullet_list + def test_tab_indents_only_lines_within_selection with_settings :text_formatting => 'common_mark' do visit '/projects/ecookbook/issues/new' within('form#issue-form') do + text = "line1\nline2\nline3" + start = text.index("line2") el = find('#issue_description') el.click - set_textarea_value 'issue_description', "- parent\n - child", selection: [0, text.length] - el.send_keys([:shift, :tab]) - assert_equal "- parent\n- child", el.value - end - end - end - - def test_shift_tab_removes_partial_indent_on_common_mark_list - # Removes only as many spaces as exist when indent is less than the step size - with_settings :text_formatting => 'common_mark' do - visit '/projects/ecookbook/issues/new' - - within('form#issue-form') do - el = find('#issue_description') - el.click - set_textarea_value 'issue_description', "- parent\n - child", selection: [0, text.length] - el.send_keys([:shift, :tab]) - assert_equal "- parent\n- child", el.value - end - end - end - - def test_shift_tab_does_nothing_on_common_mark_list_with_no_indent - with_settings :text_formatting => 'common_mark' do - visit '/projects/ecookbook/issues/new' - - within('form#issue-form') do - el = find('#issue_description') - el.click - set_textarea_value 'issue_description', "- item", selection: [0, text.length] - el.send_keys([:shift, :tab]) - assert_equal "- item", el.value + set_textarea_value 'issue_description', text, selection: [start, start + "line2".length] + el.send_keys(:tab) + assert_equal "line1\n line2\nline3", el.value end end end - def test_tab_indents_multiple_lines_when_selected + def test_tab_does_not_indent_without_selection with_settings :text_formatting => 'common_mark' do visit '/projects/ecookbook/issues/new' within('form#issue-form') do + fill_in 'Description', with: "hello" el = find('#issue_description') el.click - set_textarea_value 'issue_description', "- item1\n- item2", selection: [0, text.length] el.send_keys(:tab) - assert_equal " - item1\n - item2", el.value + assert_equal "hello", el.value end end end - def test_tab_does_not_indent_without_list_or_selection + def test_shift_tab_unindents_selected_text with_settings :text_formatting => 'common_mark' do visit '/projects/ecookbook/issues/new' within('form#issue-form') do - fill_in 'Description', with: "normal text" + text = " hello\n world" el = find('#issue_description') el.click - el.send_keys(:tab) - assert_equal "normal text", el.value + set_textarea_value 'issue_description', text, selection: [0, text.length] + el.send_keys([:shift, :tab]) + assert_equal "hello\nworld", el.value end end end - def test_tab_indents_only_selected_lines_block + def test_shift_tab_removes_partial_indent + # Removes only as many spaces as exist when indent is less than the step size with_settings :text_formatting => 'common_mark' do visit '/projects/ecookbook/issues/new' within('form#issue-form') do - text = "- item1\n- item2\n- item3" - start = text.index("- item2") - finish = start + "- item2".length + text = " hello\n world" el = find('#issue_description') el.click - set_textarea_value 'issue_description', text, selection: [start, finish] - el.send_keys(:tab) - assert_equal "- item1\n - item2\n- item3", el.value + set_textarea_value 'issue_description', text, selection: [0, text.length] + el.send_keys([:shift, :tab]) + assert_equal "hello\nworld", el.value end end end - def test_tab_indents_only_selected_lines_block_ordered_list + def test_shift_tab_does_nothing_when_no_indent with_settings :text_formatting => 'common_mark' do visit '/projects/ecookbook/issues/new' within('form#issue-form') do - text = "1. item1\n1. item2\n1. item3" - start = text.index("1. item2") + 5 - finish = start + 1 + text = "hello" el = find('#issue_description') el.click - set_textarea_value 'issue_description', text, selection: [start, finish] - el.send_keys(:tab) - assert_equal "1. item1\n 1. item2\n1. item3", el.value + set_textarea_value 'issue_description', text, selection: [0, text.length] + el.send_keys([:shift, :tab]) + assert_equal "hello", el.value end end end -- 2.54.0