Feature #39130 » 0001-WIP.patch
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" |