Feature #41294 » 0004-Separate-the-quote-reply-implementation-into-quote_reply.js.patch
| app/assets/javascripts/application.js | ||
|---|---|---|
| 1262 | 1262 |
tribute.attach(element); |
| 1263 | 1263 |
} |
| 1264 | 1264 | |
| 1265 |
function quoteReply(path, selectorForContentElement) {
|
|
| 1266 |
const contentElement = $(selectorForContentElement).get(0); |
|
| 1267 |
const quote = QuoteExtractor.extract(contentElement); |
|
| 1268 | ||
| 1269 |
$.ajax({
|
|
| 1270 |
url: path, |
|
| 1271 |
type: 'post', |
|
| 1272 |
data: { quote: quote }
|
|
| 1273 |
}); |
|
| 1274 |
} |
|
| 1275 | ||
| 1276 |
class QuoteExtractor {
|
|
| 1277 |
static extract(targetElement) {
|
|
| 1278 |
return new QuoteExtractor(targetElement).extract(); |
|
| 1279 |
} |
|
| 1280 | ||
| 1281 |
constructor(targetElement) {
|
|
| 1282 |
this.targetElement = targetElement; |
|
| 1283 |
this.selection = window.getSelection(); |
|
| 1284 |
} |
|
| 1285 | ||
| 1286 |
extract() {
|
|
| 1287 |
const range = this.selectedRange; |
|
| 1288 | ||
| 1289 |
if (!range) {
|
|
| 1290 |
return null; |
|
| 1291 |
} |
|
| 1292 | ||
| 1293 |
if (!this.targetElement.contains(range.startContainer)) {
|
|
| 1294 |
range.setStartBefore(this.targetElement); |
|
| 1295 |
} |
|
| 1296 |
if (!this.targetElement.contains(range.endContainer)) {
|
|
| 1297 |
range.setEndAfter(this.targetElement); |
|
| 1298 |
} |
|
| 1299 | ||
| 1300 |
return this.formatRange(range); |
|
| 1301 |
} |
|
| 1302 | ||
| 1303 |
formatRange(range) {
|
|
| 1304 |
return range.toString().trim(); |
|
| 1305 |
} |
|
| 1306 | ||
| 1307 |
get selectedRange() {
|
|
| 1308 |
if (!this.isSelected) {
|
|
| 1309 |
return null; |
|
| 1310 |
} |
|
| 1311 | ||
| 1312 |
// Retrive the first range that intersects with the target element. |
|
| 1313 |
// NOTE: Firefox allows to select multiple ranges in the document. |
|
| 1314 |
for (let i = 0; i < this.selection.rangeCount; i++) {
|
|
| 1315 |
let range = this.selection.getRangeAt(i); |
|
| 1316 |
if (range.intersectsNode(this.targetElement)) {
|
|
| 1317 |
return range; |
|
| 1318 |
} |
|
| 1319 |
} |
|
| 1320 |
return null; |
|
| 1321 |
} |
|
| 1322 | ||
| 1323 |
get isSelected() {
|
|
| 1324 |
return this.selection.containsNode(this.targetElement, true); |
|
| 1325 |
} |
|
| 1326 |
} |
|
| 1327 | ||
| 1328 | 1265 |
$(document).ready(setupAjaxIndicator); |
| 1329 | 1266 |
$(document).ready(hideOnLoad); |
| 1330 | 1267 |
$(document).ready(addFormObserversForDoubleSubmit); |
| app/assets/javascripts/quote_reply.js | ||
|---|---|---|
| 1 |
function quoteReply(path, selectorForContentElement) {
|
|
| 2 |
const contentElement = $(selectorForContentElement).get(0); |
|
| 3 |
const quote = QuoteExtractor.extract(contentElement); |
|
| 4 | ||
| 5 |
$.ajax({
|
|
| 6 |
url: path, |
|
| 7 |
type: 'post', |
|
| 8 |
data: { quote: quote }
|
|
| 9 |
}); |
|
| 10 |
} |
|
| 11 | ||
| 12 |
class QuoteExtractor {
|
|
| 13 |
static extract(targetElement) {
|
|
| 14 |
return new QuoteExtractor(targetElement).extract(); |
|
| 15 |
} |
|
| 16 | ||
| 17 |
constructor(targetElement) {
|
|
| 18 |
this.targetElement = targetElement; |
|
| 19 |
this.selection = window.getSelection(); |
|
| 20 |
} |
|
| 21 | ||
| 22 |
extract() {
|
|
| 23 |
const range = this.selectedRange; |
|
| 24 | ||
| 25 |
if (!range) {
|
|
| 26 |
return null; |
|
| 27 |
} |
|
| 28 | ||
| 29 |
if (!this.targetElement.contains(range.startContainer)) {
|
|
| 30 |
range.setStartBefore(this.targetElement); |
|
| 31 |
} |
|
| 32 |
if (!this.targetElement.contains(range.endContainer)) {
|
|
| 33 |
range.setEndAfter(this.targetElement); |
|
| 34 |
} |
|
| 35 | ||
| 36 |
return this.formatRange(range); |
|
| 37 |
} |
|
| 38 | ||
| 39 |
formatRange(range) {
|
|
| 40 |
return range.toString().trim(); |
|
| 41 |
} |
|
| 42 | ||
| 43 |
get selectedRange() {
|
|
| 44 |
if (!this.isSelected) {
|
|
| 45 |
return null; |
|
| 46 |
} |
|
| 47 | ||
| 48 |
// Retrive the first range that intersects with the target element. |
|
| 49 |
// NOTE: Firefox allows to select multiple ranges in the document. |
|
| 50 |
for (let i = 0; i < this.selection.rangeCount; i++) {
|
|
| 51 |
let range = this.selection.getRangeAt(i); |
|
| 52 |
if (range.intersectsNode(this.targetElement)) {
|
|
| 53 |
return range; |
|
| 54 |
} |
|
| 55 |
} |
|
| 56 |
return null; |
|
| 57 |
} |
|
| 58 | ||
| 59 |
get isSelected() {
|
|
| 60 |
return this.selection.containsNode(this.targetElement, true); |
|
| 61 |
} |
|
| 62 |
} |
|
| app/views/issues/show.html.erb | ||
|---|---|---|
| 1 |
<% content_for :header_tags do %> |
|
| 2 |
<%= javascript_include_tag 'quote_reply' %> |
|
| 3 |
<% end %> |
|
| 4 | ||
| 1 | 5 |
<%= render :partial => 'action_menu' %> |
| 2 | 6 | |
| 3 | 7 |
<h2 class="inline-block"><%= issue_heading(@issue) %></h2><%= issue_status_type_badge(@issue.status) %> |
| app/views/messages/show.html.erb | ||
|---|---|---|
| 1 |
<% content_for :header_tags do %> |
|
| 2 |
<%= javascript_include_tag 'quote_reply' %> |
|
| 3 |
<% end %> |
|
| 4 | ||
| 1 | 5 |
<%= board_breadcrumb(@message) %> |
| 2 | 6 | |
| 3 | 7 |
<div class="contextual"> |