summaryrefslogtreecommitdiffstats
path: root/extensions/BugModal
diff options
context:
space:
mode:
Diffstat (limited to 'extensions/BugModal')
-rw-r--r--extensions/BugModal/template/en/default/bug_modal/activity_stream.html.tmpl15
-rw-r--r--extensions/BugModal/template/en/default/bug_modal/edit.html.tmpl4
-rw-r--r--extensions/BugModal/template/en/default/bug_modal/new_comment.html.tmpl15
-rw-r--r--extensions/BugModal/web/bug_modal.css49
-rw-r--r--extensions/BugModal/web/bug_modal.js212
5 files changed, 262 insertions, 33 deletions
diff --git a/extensions/BugModal/template/en/default/bug_modal/activity_stream.html.tmpl b/extensions/BugModal/template/en/default/bug_modal/activity_stream.html.tmpl
index 08c6b5b64..340bb6f81 100644
--- a/extensions/BugModal/template/en/default/bug_modal/activity_stream.html.tmpl
+++ b/extensions/BugModal/template/en/default/bug_modal/activity_stream.html.tmpl
@@ -244,8 +244,17 @@
[% END %]
[% BLOCK comment_body %]
- <pre class="comment-text [%= "bz_private" IF comment.is_private %]" id="ct-[% comment.count FILTER none %]"
- [% IF comment.collapsed +%] style="display:none"[% END ~%]
+ [% IF comment.is_markdown %]
+ [% comment_tag = 'div' %]
+ [% ELSE %]
+ [% comment_tag = 'pre' %]
+ [% END %]
+
+ <[% comment_tag FILTER none %] class="comment-text [%= "bz_private" IF comment.is_private %]"
+ id="ct-[% comment.count FILTER none %]"
+ data-uniqueid="[% comment.id FILTER none %]"
+ [% IF comment.is_markdown +%] data-ismarkdown="true" [% END ~%]
+ [% IF comment.collapsed +%] style="display:none"[% END ~%]
>[% FILTER collapse %]
[% IF comment.is_about_attachment && comment.attachment.is_image ~%]
<a href="attachment.cgi?id=[% comment.attachment.id FILTER none %]"
@@ -253,7 +262,7 @@
class="lightbox"><img src="extensions/BugModal/web/image.png" width="16" height="16"></a>
[% END %]
[% END %]
- [%~ comment.body_full FILTER quoteUrls(bug, comment) ~%]</pre>
+ [%~ comment.body_full FILTER renderComment(bug, comment) ~%]</[% comment_tag FILTER none %]>
[% END %]
[%
diff --git a/extensions/BugModal/template/en/default/bug_modal/edit.html.tmpl b/extensions/BugModal/template/en/default/bug_modal/edit.html.tmpl
index e926c04b4..e2e8bc124 100644
--- a/extensions/BugModal/template/en/default/bug_modal/edit.html.tmpl
+++ b/extensions/BugModal/template/en/default/bug_modal/edit.html.tmpl
@@ -202,7 +202,7 @@
no_label = 1
hide_on_edit = 1
%]
- <h1 id="field-value-short_desc">[% bug.short_desc FILTER quoteUrls(bug) FILTER wbr %]</h1>
+ <h1 id="field-value-short_desc">[% bug.short_desc FILTER renderComment(bug, undef, 1) FILTER wbr %]</h1>
[% END %]
[%# alias %]
@@ -1191,7 +1191,7 @@
[% END %]
</div>
[% END %]
- <pre id="user-story">[% bug.cf_user_story FILTER quoteUrls(bug) %]</pre>
+ <div id="user-story" class="comment-text">[% bug.cf_user_story FILTER renderComment(bug, undef) %]</div>
[% IF user.id %]
<textarea id="cf_user_story" name="cf_user_story" style="display:none" rows="10" cols="80">
[%~ bug.cf_user_story FILTER html ~%]
diff --git a/extensions/BugModal/template/en/default/bug_modal/new_comment.html.tmpl b/extensions/BugModal/template/en/default/bug_modal/new_comment.html.tmpl
index 63663b4d5..63c8cf197 100644
--- a/extensions/BugModal/template/en/default/bug_modal/new_comment.html.tmpl
+++ b/extensions/BugModal/template/en/default/bug_modal/new_comment.html.tmpl
@@ -45,12 +45,19 @@
<textarea rows="5" cols="80" name="comment" id="comment" aria-labelledby="comment-edit-tab"></textarea>
</div>
<div id="comment-preview-tabpanel" class="comment-tabpanel" role="tabpanel" aria-labelledby="comment-preview-tab" style="display:none">
- <pre id="comment-preview" class="comment-text"></pre>
+ <div id="comment-preview" class="comment-text"></div>
</div>
- <div id="bugzilla-etiquette">
- <a href="page.cgi?id=etiquette.html" target="_blank" tabindex="-1">
- Comments Subject to Etiquette and Contributor Guidelines</a>
+ <div id="add-comment-tips">
+ <div id="comment-markdown-tip">
+ <img src="extensions/BMO/web/images/notice.png" width="16" height="16">
+ <a href="https://guides.github.com/features/mastering-markdown/" target="_blank">Markdown styling now supported</a>
+ </div>
+
+ <div id="bugzilla-etiquette">
+ <a href="page.cgi?id=etiquette.html" target="_blank" tabindex="-1">
+ Comments Subject to Etiquette and Contributor Guidelines</a>
+ </div>
</div>
<div id="after-comment-commit-button">
diff --git a/extensions/BugModal/web/bug_modal.css b/extensions/BugModal/web/bug_modal.css
index ee50c6b77..bf291d3b6 100644
--- a/extensions/BugModal/web/bug_modal.css
+++ b/extensions/BugModal/web/bug_modal.css
@@ -296,7 +296,6 @@ input[type="number"] {
#user-story {
margin: 0;
- white-space: pre-wrap;
min-height: 2em;
}
@@ -630,7 +629,8 @@ body.platform-Win32 .comment-text, body.platform-Win64 .comment-text {
font-family: "Fira Mono", monospace;
}
-.comment-text span.quote, .comment-text span.quote_wrapped {
+.comment-text span.quote, .comment-text span.quote_wrapped,
+div.comment-text pre {
background: #eee !important;
color: #444 !important;
display: block !important;
@@ -644,6 +644,40 @@ body.platform-Win32 .comment-text, body.platform-Win64 .comment-text {
border: 1px dashed darkred;
}
+/* Markdown comments */
+div.comment-text {
+ white-space: normal;
+ padding: 0 8px 0 8px;
+ font-family: inherit !important;
+}
+
+div.comment-text code {
+ color: #444;
+ background-color: #eee;
+ font-size: 13px;
+ font-family: "Fira Mono","Droid Sans Mono",Menlo,Monaco,"Courier New",monospace;
+}
+
+div.comment-text table {
+ border-collapse: collapse;
+}
+
+div.comment-text th, div.comment-text td {
+ padding: 5px 10px;
+ border: 1px solid #ccc;
+}
+
+div.comment-text hr {
+ display: block !important;
+}
+
+div.comment-text blockquote {
+ background: #fcfcfc;
+ border-left: 5px solid #ccc;
+ margin: 1.5em 10px;
+ padding: 0.5em 10px;
+}
+
.comment-tags {
padding: 0 8px 2px 8px !important;
}
@@ -717,11 +751,16 @@ body.platform-Win32 .comment-text, body.platform-Win64 .comment-text {
margin-top: 20px;
}
-#add-comment-private,
-#bugzilla-etiquette {
+#add-comment-private {
float: right;
}
+#add-comment-tips {
+ display: flex;
+ justify-content: space-between;
+ margin-bottom: 1em;
+}
+
#comment {
border: 1px solid #ccc;
}
@@ -730,7 +769,7 @@ body.platform-Win32 .comment-text, body.platform-Win64 .comment-text {
clear: both;
width: 100%;
box-sizing: border-box !important;
- margin: 0 0 1em;
+ margin: 0 0 0.5em;
max-width: 1024px;
}
diff --git a/extensions/BugModal/web/bug_modal.js b/extensions/BugModal/web/bug_modal.js
index a4ae83d72..19b5bfa2f 100644
--- a/extensions/BugModal/web/bug_modal.js
+++ b/extensions/BugModal/web/bug_modal.js
@@ -858,31 +858,54 @@ $(function() {
var prefix = "(In reply to " + comment_author + " from comment #" + comment_id + ")\n";
var reply_text = "";
- if (BUGZILLA.user.settings.quote_replies == 'quoted_reply') {
- var text = $('#ct-' + comment_id).text();
- reply_text = prefix + wrapReplyText(text);
- } else if (BUGZILLA.user.settings.quote_replies == 'simply_reply') {
- reply_text = prefix;
+
+ var quoteMarkdown = function($comment) {
+ const uid = $comment.data('uniqueid');
+ bugzilla_ajax(
+ {
+ url: `rest/bug/comment/${uid}`,
+ },
+ (data) => {
+ const quoted = data['comments'][uid]['text'].replace(/\n/g, "\n > ");
+ reply_text = `${prefix}\n > ${quoted}`;
+ populateNewComment();
+ }
+ );
}
- // quoting a private comment, check the 'private' cb
- $('#add-comment-private-cb').prop('checked',
- $('#add-comment-private-cb:checked').length || $('#is-private-' + comment_id + ':checked').length);
+ var populateNewComment = function() {
+ // quoting a private comment, check the 'private' cb
+ $('#add-comment-private-cb').prop('checked',
+ $('#add-comment-private-cb:checked').length || $('#is-private-' + comment_id + ':checked').length);
- // remove embedded links to attachment details
- reply_text = reply_text.replace(/(attachment\s+\d+)(\s+\[[^\[\n]+\])+/gi, '$1');
+ // remove embedded links to attachment details
+ reply_text = reply_text.replace(/(attachment\s+\d+)(\s+\[[^\[\n]+\])+/gi, '$1');
- $.scrollTo($('#comment'), function() {
- if ($('#comment').val() != reply_text) {
- $('#comment').val($('#comment').val() + reply_text);
- }
+ $.scrollTo($('#comment'), function() {
+ if ($('#comment').val() != reply_text) {
+ $('#comment').val($('#comment').val() + reply_text);
+ }
- if (BUGZILLA.user.settings.autosize_comments) {
- autosize.update($('#comment'));
- }
+ if (BUGZILLA.user.settings.autosize_comments) {
+ autosize.update($('#comment'));
+ }
- $('#comment').focus();
- });
+ $('#comment').trigger('input').focus();
+ });
+ }
+
+ if (BUGZILLA.user.settings.quote_replies == 'quoted_reply') {
+ var $comment = $('#ct-' + comment_id);
+ if ($comment.attr('data-ismarkdown')) {
+ quoteMarkdown($comment);
+ } else {
+ reply_text = prefix + wrapReplyText($comment.text());
+ populateNewComment();
+ }
+ } else if (BUGZILLA.user.settings.quote_replies == 'simply_reply') {
+ reply_text = prefix;
+ populateNewComment();
+ }
});
if (BUGZILLA.user.settings.autosize_comments) {
@@ -1320,12 +1343,163 @@ $(function() {
saveBugComment(event.target.value);
});
+ function smartLinkPreviews() {
+ const filterUnique = (value, index, array) => value && array.indexOf(value) === index;
+ const reduceListToMap = (all, one) => { all[one['id']] = one; return all; };
+
+ const getResourceId = anchor => {
+ if (['/bug/', '/attachment/'].some((path) => anchor.pathname.startsWith(path))) {
+ return anchor.pathname.split('/')[2];
+ } else {
+ return (new URL(anchor.href)).searchParams.get("id");
+ }
+ };
+
+ const findLinkElements = pathnames => {
+ return (
+ Array
+ .from(document.querySelectorAll('.comment-text a'))
+ .filter(anchor => {
+ return (
+ `${anchor.origin}/` === BUGZILLA.constant.URL_BASE &&
+ pathnames.some((p) => anchor.pathname.startsWith(p)) &&
+ /^\d+$/.test(getResourceId(anchor))
+ )
+ })
+ .filter(anchor =>
+ // Get only links created by markdown or private links.
+ !anchor.hasAttribute('title') || anchor.classList.contains('bz_private_link')
+ )
+ .map(anchor => {
+ return {
+ id: getResourceId(anchor),
+ element: anchor
+ }
+ })
+ )
+ };
+
+ const enhanceBugLinks = () => {
+ let bugLinks = findLinkElements(['/show_bug.cgi', '/bug/']);
+ let bugIds = bugLinks.map((bug) => parseInt(bug['id'])).filter(filterUnique).join(',');
+ let params = $.param({
+ Bugzilla_api_token: BUGZILLA.api_token,
+ id: bugIds,
+ include_fields: 'id,summary,status,resolution,is_open'
+ });
+
+ if(!bugIds) return;
+
+ fetch(`/rest/bug?${params}`)
+ .then(response => {
+ if(response.ok){
+ return response.json();
+ }
+ throw new Error(`/rest/bug?ids=${bugIds} response not ok`);
+ })
+ .then(responseJson => {
+ return responseJson.bugs.reduce(reduceListToMap, {});
+ })
+ .then(bugs => {
+ bugLinks.forEach(bugLink => {
+ let bug = bugs[bugLink['id']];
+ if(!bug) return;
+
+ bugLink.element.setAttribute(
+ "title", `${bug.status} ${bug.resolution} - ${bug.summary}`
+ );
+ bugLink.element.classList.add('bz_bug_link');
+ bugLink.element.classList.add(`bz_status_${bug.status}`);
+ if(!bug.is_open) {
+ bugLink.element.classList.add('bz_closed');
+ }
+ $(bugLink.element).tooltip({
+ position: { my: "left top+8", at: "left bottom", collision: "flipfit" },
+ show: { effect: 'none' },
+ hide: { effect: 'none' }
+ });
+ });
+ })
+ .catch(e => console.log(e));
+ };
+
+ const enhanceAttachmentLinks = () => {
+ let attachmentLinks = findLinkElements(['/attachment.cgi']);
+ let attachmentIds = (
+ attachmentLinks.map(attachment => parseInt(attachment['id'])).filter(filterUnique)
+ );
+ let params = $.param({
+ Bugzilla_api_token: BUGZILLA.api_token,
+ include_fields: 'id,description,is_obsolete'
+ });
+
+ if(!attachmentIds) return;
+
+ // Fetch all attachments for this bug only. This endpoint filters out
+ // attachments the user can't see for us (e.g. ones marked private).
+ // This one request will likely retrieve most of the attachments we need.
+ fetch(`/rest/bug/${BUGZILLA.bug_id}/attachment?${params}`)
+ .then(response => {
+ if(response.ok){
+ return response.json();
+ }
+ throw Error(`/rest/bug/${BUGZILLA.bug_id}/attachment response not ok`);
+ })
+ .then(responseJson => {
+ return responseJson['bugs'][BUGZILLA.bug_id] || [];
+ })
+ .then(attachments => {
+ // The BMO rest API that lets us batch request attachment ids unfortunatley
+ // fails the whole batch if the user is unable to view any of the attachments.
+ // So, we query each attachment id individually and group them as a promsie.
+ let missingAttachments = (
+ attachmentIds
+ .filter(id => !attachments.map(attachment => attachment.id).includes(id))
+ .map(attachmentId => {
+ return (
+ fetch(`/rest/bug/attachment/${attachmentId}?${params}`)
+ .then((response) => {
+ // It's ok if the request failed.
+ return response.json();
+ })
+ .then(responseJson => {
+ // May be undefined.
+ return responseJson['attachments'][attachmentId];
+ })
+ );
+ })
+ );
+ return Promise.all(attachments.concat(missingAttachments));
+ })
+ .then(attachments => {
+ // Remove undefined attachments and convert from list to dictonary mapped by id.
+ return attachments.filter(filterUnique).reduce(reduceListToMap, {});
+ })
+ .then(attachments => {
+ // Now we have all attachment data the user is able to see.
+ attachmentLinks.forEach(attachmentLink => {
+ let attachment = attachments[attachmentLink.id];
+ if(!attachment) return;
+
+ attachmentLink.element.setAttribute("title", attachment.description);
+ if(attachment.is_obsolete){
+ attachmentLink.element.classList.add('bz_obsolete');
+ }
+ });
+ })
+ .catch(e => console.log(e));
+ };
+ enhanceBugLinks();
+ enhanceAttachmentLinks();
+ }
+
// finally switch to edit mode if we navigate back to a page that was editing
$(window).on('pageshow', restoreEditMode);
$(window).on('pageshow', restoreSavedBugComment);
$(window).on('focus', restoreSavedBugComment);
restoreEditMode();
restoreSavedBugComment();
+ smartLinkPreviews();
});
function confirmUnsafeURL(url) {