Project

General

Profile

Feature #39130 » 0001-WIP.patch

Marius BĂLTEANU, 2025-07-24 23:59

View differences:

app/controllers/issues_controller.rb
198 198
  def update
199 199
    return unless update_issue_from_params
200 200

  
201
    if params[:sourcepos].present?
202
      source_pos = params[:sourcepos] # e.g., "10:1-10:15"
203
      is_checked = params[:checked]
204

  
205
      line_number = source_pos.split(':').first.to_i
206

  
207
      description_lines = @issue.description.lines
208
      target_line = description_lines[line_number - 1]
209

  
210
      if target_line
211
        if is_checked
212
          # Change '[ ]' to '[x]'
213
          target_line.sub!(/\[\s\]/, '[x]')
214
        else
215
          # Change '[x]' to '[ ]'
216
          target_line.sub!(/\[x\]/i, '[ ]')
217
        end
218
      end
219

  
220
      @issue.description = description_lines.join
221
    end
222

  
223
    # if params[:source]
201 224
    attachments = params[:attachments] || params.dig(:issue, :uploads)
202 225
    if @issue.attachments_addable?
203 226
      @issue.save_attachments(attachments)
app/javascript/controllers/task_list_controller.js
1
import {Controller} from '@hotwired/stimulus'
2

  
3
export default class extends Controller {
4
    // Define the 'updateUrl' value that we passed from the HTML
5
    static values = {
6
        id: String,
7
    };
8

  
9
    connect() {
10
        // Find all checkboxes within this controller's scope and enable them.
11
        this.element.querySelectorAll('input[type="checkbox"].task-list-item-checkbox').forEach(checkbox => {
12
            checkbox.disabled = false;
13

  
14
            checkbox.addEventListener('change', (event) => {
15
                const isChecked = event.target.checked;
16
                const sourcePosition = checkbox.parentElement.dataset.sourcepos;
17

  
18
                if (!sourcePosition) {
19
                    console.error('Task item is missing data-sourcepos attribute.');
20
                    // Optionally revert the checkbox and show an error
21
                    event.target.checked = !isChecked;
22
                    return;
23
                }
24

  
25
                this.updateTaskListItem(this.idValue, sourcePosition, isChecked);
26
            });
27
        });
28
    }
29

  
30
    async updateTaskListItem(issueId, sourcePosition, isChecked) {
31

  
32
        // You need to get the CSRF token for Rails
33
        const csrfToken = document.querySelector('meta[name="csrf-token"]')?.content;
34

  
35
        try {
36
            const response = await fetch(`/issues/${issueId}`, {
37
                method: 'PATCH',
38
                headers: {
39
                    'Content-Type': 'application/json',
40
                    'Accept': 'application/json',
41
                    'X-CSRF-Token': csrfToken,
42
                },
43
                body: JSON.stringify({
44
                    sourcepos: sourcePosition,
45
                    checked: isChecked,
46
                }),
47
            });
48

  
49
            if (!response.ok) {
50
                // Handle server error: revert the checkbox state and alert the user
51
                throw new Error();
52
            }
53

  
54
            console.log('Task updated successfully!');
55

  
56
        } catch (error) {
57
            console.error('Error:', error);
58
            // Revert the checkbox state
59
        }
60
    }
61
}
62

  
app/views/issues/show.html.erb
98 98
  </div>
99 99

  
100 100
  <p><strong><%=l(:field_description)%></strong></p>
101
  <div id="issue_description_wiki" class="wiki" data-quote-reply-target="content">
101
  <div id="issue_description_wiki" class="wiki"
102
       data-quote-reply-target="content"
103
       data-controller="task-list"
104
       data-task-list-id-value="<%= @issue.id %>">
102 105
  <%= textilizable @issue, :description, :attachments => @issue.attachments %>
103 106
  </div>
104 107
</div>
lib/redmine/wiki_formatting/common_mark/formatter.rb
47 47
          github_pre_lang: false,
48 48
          hardbreaks: Redmine::Configuration['common_mark_enable_hardbreaks'] == true,
49 49
          tasklist_classes: true,
50
          sourcepos: true,
50 51
        }.freeze,
51 52
        commonmarker_plugins: {
52 53
          syntax_highlighter: nil
lib/redmine/wiki_formatting/common_mark/markdown_filter.rb
33 33

  
34 34
        def call
35 35
          html = Commonmarker.to_html(@text, options: {
36
                                        extension: extensions,
36
            extension: extensions,
37 37
            render: render_options,
38 38
            parse: parse_options
39
                                      }, plugins: plugins)
39
          }, plugins: plugins)
40 40

  
41 41
          html.rstrip!
42 42
          html
lib/redmine/wiki_formatting/common_mark/sanitization_filter.rb
112 112

  
113 113
          # allow `id` in li element for footnotes
114 114
          # allow `class` in li element for task list items
115
          allowlist[:attributes]["li"] = %w(id class)
115
          allowlist[:attributes]["li"] = %w(id class data-sourcepos)
116 116
          allowlist[:transformers].push lambda{|env|
117 117
            node = env[:node]
118 118
            return unless node.name == "li"
    (1-1/1)