pull-requests: allow markup rendered description.
This commit is contained in:
parent
e32681449c
commit
bafbc2b040
10 changed files with 819 additions and 449 deletions
|
|
@ -42,6 +42,10 @@ def includeme(config):
|
|||
name='goto_switcher_data',
|
||||
pattern='/_goto_data')
|
||||
|
||||
config.add_route(
|
||||
name='markup_preview',
|
||||
pattern='/_markup_preview')
|
||||
|
||||
# register our static links via redirection mechanism
|
||||
routing_links.connect_redirection_links(config)
|
||||
|
||||
|
|
|
|||
|
|
@ -27,7 +27,8 @@ from pyramid.view import view_config
|
|||
from rhodecode.apps._base import BaseAppView
|
||||
from rhodecode.lib import helpers as h
|
||||
from rhodecode.lib.auth import (
|
||||
LoginRequired, NotAnonymous, HasRepoGroupPermissionAnyDecorator)
|
||||
LoginRequired, NotAnonymous, HasRepoGroupPermissionAnyDecorator,
|
||||
CSRFRequired)
|
||||
from rhodecode.lib.index import searcher_from_config
|
||||
from rhodecode.lib.utils2 import safe_unicode, str2bool, safe_int
|
||||
from rhodecode.lib.ext_json import json
|
||||
|
|
@ -425,3 +426,21 @@ class HomeView(BaseAppView):
|
|||
c.repo_groups_data = json.dumps(repo_group_data)
|
||||
|
||||
return self._get_template_context(c)
|
||||
|
||||
@LoginRequired()
|
||||
@CSRFRequired()
|
||||
@view_config(
|
||||
route_name='markup_preview', request_method='POST',
|
||||
renderer='string', xhr=True)
|
||||
def markup_preview(self):
|
||||
# Technically a CSRF token is not needed as no state changes with this
|
||||
# call. However, as this is a POST is better to have it, so automated
|
||||
# tools don't flag it as potential CSRF.
|
||||
# Post is required because the payload could be bigger than the maximum
|
||||
# allowed by GET.
|
||||
|
||||
text = self.request.POST.get('text')
|
||||
renderer = self.request.POST.get('renderer') or 'rst'
|
||||
if text:
|
||||
return h.render(text, renderer=renderer, mentions=True)
|
||||
return ''
|
||||
|
|
|
|||
|
|
@ -1418,9 +1418,6 @@ table.integrations {
|
|||
margin-top: @textmargin;
|
||||
margin-bottom: @textmargin;
|
||||
}
|
||||
.pr-description {
|
||||
white-space:pre-wrap;
|
||||
}
|
||||
|
||||
.pr-reviewer-rules {
|
||||
padding: 10px 0px 20px 0px;
|
||||
|
|
@ -2405,3 +2402,65 @@ input[type=radio] {
|
|||
height: 16px;
|
||||
width: 16px;
|
||||
}
|
||||
|
||||
|
||||
.markup-form .clearfix {
|
||||
.border-radius(@border-radius);
|
||||
margin: 0px;
|
||||
}
|
||||
|
||||
.markup-form-area {
|
||||
padding: 8px 12px;
|
||||
border: 1px solid @grey4;
|
||||
.border-radius(@border-radius);
|
||||
}
|
||||
|
||||
.markup-form-area-header .nav-links {
|
||||
display: flex;
|
||||
flex-flow: row wrap;
|
||||
-webkit-flex-flow: row wrap;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.markup-form-area-footer {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.markup-form-area-footer .toolbar {
|
||||
|
||||
}
|
||||
|
||||
// markup Form
|
||||
div.markup-form {
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.markup-form strong {
|
||||
display: block;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.markup-form textarea {
|
||||
width: 100%;
|
||||
height: 100px;
|
||||
font-family: 'Monaco', 'Courier', 'Courier New', monospace;
|
||||
}
|
||||
|
||||
form.markup-form {
|
||||
margin-top: 10px;
|
||||
margin-left: 10px;
|
||||
}
|
||||
|
||||
.markup-form .comment-block-ta,
|
||||
.markup-form .preview-box {
|
||||
.border-radius(@border-radius);
|
||||
.box-sizing(border-box);
|
||||
background-color: white;
|
||||
}
|
||||
|
||||
.markup-form .preview-box.unloaded {
|
||||
height: 50px;
|
||||
text-align: center;
|
||||
padding: 20px;
|
||||
background-color: white;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -76,6 +76,7 @@ function registerRCRoutes() {
|
|||
pyroutes.register('admin_settings_search', '/_admin/settings/search', []);
|
||||
pyroutes.register('admin_settings_labs', '/_admin/settings/labs', []);
|
||||
pyroutes.register('admin_settings_labs_update', '/_admin/settings/labs/update', []);
|
||||
pyroutes.register('admin_settings_automation', '/_admin/_admin/settings/automation', []);
|
||||
pyroutes.register('admin_permissions_application', '/_admin/permissions/application', []);
|
||||
pyroutes.register('admin_permissions_application_update', '/_admin/permissions/application/update', []);
|
||||
pyroutes.register('admin_permissions_global', '/_admin/permissions/global', []);
|
||||
|
|
@ -141,6 +142,7 @@ function registerRCRoutes() {
|
|||
pyroutes.register('user_group_autocomplete_data', '/_user_groups', []);
|
||||
pyroutes.register('repo_list_data', '/_repos', []);
|
||||
pyroutes.register('goto_switcher_data', '/_goto_data', []);
|
||||
pyroutes.register('markup_preview', '/_markup_preview', []);
|
||||
pyroutes.register('journal', '/_admin/journal', []);
|
||||
pyroutes.register('journal_rss', '/_admin/journal/rss', []);
|
||||
pyroutes.register('journal_atom', '/_admin/journal/atom', []);
|
||||
|
|
@ -222,6 +224,7 @@ function registerRCRoutes() {
|
|||
pyroutes.register('edit_repo_advanced_locking', '/%(repo_name)s/settings/advanced/locking', ['repo_name']);
|
||||
pyroutes.register('edit_repo_advanced_journal', '/%(repo_name)s/settings/advanced/journal', ['repo_name']);
|
||||
pyroutes.register('edit_repo_advanced_fork', '/%(repo_name)s/settings/advanced/fork', ['repo_name']);
|
||||
pyroutes.register('edit_repo_advanced_hooks', '/%(repo_name)s/settings/advanced/hooks', ['repo_name']);
|
||||
pyroutes.register('edit_repo_caches', '/%(repo_name)s/settings/caches', ['repo_name']);
|
||||
pyroutes.register('edit_repo_perms', '/%(repo_name)s/settings/permissions', ['repo_name']);
|
||||
pyroutes.register('edit_repo_maintenance', '/%(repo_name)s/settings/maintenance', ['repo_name']);
|
||||
|
|
@ -275,6 +278,7 @@ function registerRCRoutes() {
|
|||
pyroutes.register('search', '/_admin/search', []);
|
||||
pyroutes.register('search_repo', '/%(repo_name)s/search', ['repo_name']);
|
||||
pyroutes.register('user_profile', '/_profiles/%(username)s', ['username']);
|
||||
pyroutes.register('user_group_profile', '/_profile_user_group/%(user_group_name)s', ['user_group_name']);
|
||||
pyroutes.register('my_account_profile', '/_admin/my_account/profile', []);
|
||||
pyroutes.register('my_account_edit', '/_admin/my_account/edit', []);
|
||||
pyroutes.register('my_account_update', '/_admin/my_account/update', []);
|
||||
|
|
|
|||
|
|
@ -231,6 +231,68 @@ var initCodeMirror = function(textAreadId, resetUrl, focus, options) {
|
|||
return myCodeMirror;
|
||||
};
|
||||
|
||||
|
||||
var initMarkupCodeMirror = function(textAreadId, focus, options) {
|
||||
var initialHeight = 100;
|
||||
|
||||
var ta = $(textAreadId).get(0);
|
||||
if (focus === undefined) {
|
||||
focus = true;
|
||||
}
|
||||
|
||||
// default options
|
||||
var codeMirrorOptions = {
|
||||
lineNumbers: false,
|
||||
indentUnit: 4,
|
||||
viewportMargin: 30,
|
||||
// this is a trick to trigger some logic behind codemirror placeholder
|
||||
// it influences styling and behaviour.
|
||||
placeholder: " ",
|
||||
lineWrapping: true,
|
||||
autofocus: focus
|
||||
};
|
||||
|
||||
if (options !== undefined) {
|
||||
// extend with custom options
|
||||
codeMirrorOptions = $.extend(true, codeMirrorOptions, options);
|
||||
}
|
||||
|
||||
var cm = CodeMirror.fromTextArea(ta, codeMirrorOptions);
|
||||
cm.setSize(null, initialHeight);
|
||||
cm.setOption("mode", DEFAULT_RENDERER);
|
||||
CodeMirror.autoLoadMode(cm, DEFAULT_RENDERER); // load rst or markdown mode
|
||||
cmLog.debug('Loading codemirror mode', DEFAULT_RENDERER);
|
||||
|
||||
// start listening on changes to make auto-expanded editor
|
||||
cm.on("change", function(instance, changeObj) {
|
||||
var height = initialHeight;
|
||||
var lines = instance.lineCount();
|
||||
if ( lines > 6 && lines < 20) {
|
||||
height = "auto";
|
||||
}
|
||||
else if (lines >= 20){
|
||||
zheight = 20*15;
|
||||
}
|
||||
instance.setSize(null, height);
|
||||
|
||||
// detect if the change was trigger by auto desc, or user input
|
||||
var changeOrigin = changeObj.origin;
|
||||
|
||||
if (changeOrigin === "setValue") {
|
||||
cmLog.debug('Change triggered by setValue');
|
||||
}
|
||||
else {
|
||||
cmLog.debug('user triggered change !');
|
||||
// set special marker to indicate user has created an input.
|
||||
instance._userDefinedValue = true;
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
return cm;
|
||||
};
|
||||
|
||||
|
||||
var initCommentBoxCodeMirror = function(CommentForm, textAreaId, triggerActions){
|
||||
var initialHeight = 100;
|
||||
|
||||
|
|
@ -593,3 +655,196 @@ var CodeMirrorPreviewEnable = function(edit_mode) {
|
|||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/* markup form */
|
||||
(function(mod) {
|
||||
|
||||
if (typeof exports == "object" && typeof module == "object") {
|
||||
// CommonJS
|
||||
module.exports = mod();
|
||||
}
|
||||
else {
|
||||
// Plain browser env
|
||||
(this || window).MarkupForm = mod();
|
||||
}
|
||||
|
||||
})(function() {
|
||||
"use strict";
|
||||
|
||||
function MarkupForm(textareaId) {
|
||||
if (!(this instanceof MarkupForm)) {
|
||||
return new MarkupForm(textareaId);
|
||||
}
|
||||
|
||||
// bind the element instance to our Form
|
||||
$('#' + textareaId).get(0).MarkupForm = this;
|
||||
|
||||
this.withSelectorId = function(selector) {
|
||||
var selectorId = textareaId;
|
||||
return selector + '_' + selectorId;
|
||||
};
|
||||
|
||||
this.previewButton = this.withSelectorId('#preview-btn');
|
||||
this.previewContainer = this.withSelectorId('#preview-container');
|
||||
|
||||
this.previewBoxSelector = this.withSelectorId('#preview-box');
|
||||
|
||||
this.editButton = this.withSelectorId('#edit-btn');
|
||||
this.editContainer = this.withSelectorId('#edit-container');
|
||||
|
||||
this.cmBox = textareaId;
|
||||
this.cm = initMarkupCodeMirror('#' + textareaId);
|
||||
|
||||
this.previewUrl = pyroutes.url('markup_preview');
|
||||
|
||||
// FUNCTIONS and helpers
|
||||
var self = this;
|
||||
|
||||
this.getCmInstance = function(){
|
||||
return this.cm
|
||||
};
|
||||
|
||||
this.setPlaceholder = function(placeholder) {
|
||||
var cm = this.getCmInstance();
|
||||
if (cm){
|
||||
cm.setOption('placeholder', placeholder);
|
||||
}
|
||||
};
|
||||
|
||||
this.initStatusChangeSelector = function(){
|
||||
var formatChangeStatus = function(state, escapeMarkup) {
|
||||
var originalOption = state.element;
|
||||
return '<div class="flag_status ' + $(originalOption).data('status') + ' pull-left"></div>' +
|
||||
'<span>' + escapeMarkup(state.text) + '</span>';
|
||||
};
|
||||
var formatResult = function(result, container, query, escapeMarkup) {
|
||||
return formatChangeStatus(result, escapeMarkup);
|
||||
};
|
||||
|
||||
var formatSelection = function(data, container, escapeMarkup) {
|
||||
return formatChangeStatus(data, escapeMarkup);
|
||||
};
|
||||
|
||||
$(this.submitForm).find(this.statusChange).select2({
|
||||
placeholder: _gettext('Status Review'),
|
||||
formatResult: formatResult,
|
||||
formatSelection: formatSelection,
|
||||
containerCssClass: "drop-menu status_box_menu",
|
||||
dropdownCssClass: "drop-menu-dropdown",
|
||||
dropdownAutoWidth: true,
|
||||
minimumResultsForSearch: -1
|
||||
});
|
||||
$(this.submitForm).find(this.statusChange).on('change', function() {
|
||||
var status = self.getCommentStatus();
|
||||
|
||||
if (status && !self.isInline()) {
|
||||
$(self.submitButton).prop('disabled', false);
|
||||
}
|
||||
|
||||
var placeholderText = _gettext('Comment text will be set automatically based on currently selected status ({0}) ...').format(status);
|
||||
self.setPlaceholder(placeholderText)
|
||||
})
|
||||
};
|
||||
|
||||
// reset the text area into it's original state
|
||||
this.resetMarkupFormState = function(content) {
|
||||
content = content || '';
|
||||
|
||||
$(this.editContainer).show();
|
||||
$(this.editButton).parent().addClass('active');
|
||||
|
||||
$(this.previewContainer).hide();
|
||||
$(this.previewButton).parent().removeClass('active');
|
||||
|
||||
this.setActionButtonsDisabled(true);
|
||||
self.cm.setValue(content);
|
||||
self.cm.setOption("readOnly", false);
|
||||
};
|
||||
|
||||
this.previewSuccessCallback = function(o) {
|
||||
$(self.previewBoxSelector).html(o);
|
||||
$(self.previewBoxSelector).removeClass('unloaded');
|
||||
|
||||
// swap buttons, making preview active
|
||||
$(self.previewButton).parent().addClass('active');
|
||||
$(self.editButton).parent().removeClass('active');
|
||||
|
||||
// unlock buttons
|
||||
self.setActionButtonsDisabled(false);
|
||||
};
|
||||
|
||||
this.setActionButtonsDisabled = function(state) {
|
||||
$(this.editButton).prop('disabled', state);
|
||||
$(this.previewButton).prop('disabled', state);
|
||||
};
|
||||
|
||||
// lock preview/edit/submit buttons on load, but exclude cancel button
|
||||
var excludeCancelBtn = true;
|
||||
this.setActionButtonsDisabled(true);
|
||||
|
||||
// anonymous users don't have access to initialized CM instance
|
||||
if (this.cm !== undefined){
|
||||
this.cm.on('change', function(cMirror) {
|
||||
if (cMirror.getValue() === "") {
|
||||
self.setActionButtonsDisabled(true)
|
||||
} else {
|
||||
self.setActionButtonsDisabled(false)
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
$(this.editButton).on('click', function(e) {
|
||||
e.preventDefault();
|
||||
|
||||
$(self.previewButton).parent().removeClass('active');
|
||||
$(self.previewContainer).hide();
|
||||
|
||||
$(self.editButton).parent().addClass('active');
|
||||
$(self.editContainer).show();
|
||||
|
||||
});
|
||||
|
||||
$(this.previewButton).on('click', function(e) {
|
||||
e.preventDefault();
|
||||
var text = self.cm.getValue();
|
||||
|
||||
if (text === "") {
|
||||
return;
|
||||
}
|
||||
|
||||
var postData = {
|
||||
'text': text,
|
||||
'renderer': templateContext.visual.default_renderer,
|
||||
'csrf_token': CSRF_TOKEN
|
||||
};
|
||||
|
||||
// lock ALL buttons on preview
|
||||
self.setActionButtonsDisabled(true);
|
||||
|
||||
$(self.previewBoxSelector).addClass('unloaded');
|
||||
$(self.previewBoxSelector).html(_gettext('Loading ...'));
|
||||
|
||||
$(self.editContainer).hide();
|
||||
$(self.previewContainer).show();
|
||||
|
||||
// by default we reset state of comment preserving the text
|
||||
var previewFailCallback = function(data){
|
||||
alert(
|
||||
"Error while submitting preview.\n" +
|
||||
"Error code {0} ({1}).".format(data.status, data.statusText)
|
||||
);
|
||||
self.resetMarkupFormState(text)
|
||||
};
|
||||
_submitAjaxPOST(
|
||||
self.previewUrl, postData, self.previewSuccessCallback,
|
||||
previewFailCallback);
|
||||
|
||||
$(self.previewButton).parent().addClass('active');
|
||||
$(self.editButton).parent().removeClass('active');
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
return MarkupForm;
|
||||
});
|
||||
|
|
|
|||
|
|
@ -42,6 +42,29 @@ var bindToggleButtons = function() {
|
|||
});
|
||||
};
|
||||
|
||||
|
||||
|
||||
var _submitAjaxPOST = function(url, postData, successHandler, failHandler) {
|
||||
failHandler = failHandler || function() {};
|
||||
postData = toQueryString(postData);
|
||||
var request = $.ajax({
|
||||
url: url,
|
||||
type: 'POST',
|
||||
data: postData,
|
||||
headers: {'X-PARTIAL-XHR': true}
|
||||
})
|
||||
.done(function (data) {
|
||||
successHandler(data);
|
||||
})
|
||||
.fail(function (data, textStatus, errorThrown) {
|
||||
failHandler(data, textStatus, errorThrown)
|
||||
});
|
||||
return request;
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
/* Comment form for main and inline comments */
|
||||
(function(mod) {
|
||||
|
||||
|
|
@ -259,24 +282,7 @@ var bindToggleButtons = function() {
|
|||
};
|
||||
|
||||
this.submitAjaxPOST = function(url, postData, successHandler, failHandler) {
|
||||
failHandler = failHandler || function() {};
|
||||
var postData = toQueryString(postData);
|
||||
var request = $.ajax({
|
||||
url: url,
|
||||
type: 'POST',
|
||||
data: postData,
|
||||
headers: {'X-PARTIAL-XHR': true}
|
||||
})
|
||||
.done(function(data) {
|
||||
successHandler(data);
|
||||
})
|
||||
.fail(function(data, textStatus, errorThrown){
|
||||
alert(
|
||||
"Error while submitting comment.\n" +
|
||||
"Error code {0} ({1}).".format(data.status, data.statusText));
|
||||
failHandler()
|
||||
});
|
||||
return request;
|
||||
return _submitAjaxPOST(url, postData, successHandler, failHandler);
|
||||
};
|
||||
|
||||
// overwrite a submitHandler, we need to do it for inline comments
|
||||
|
|
@ -340,7 +346,11 @@ var bindToggleButtons = function() {
|
|||
self.globalSubmitSuccessCallback();
|
||||
|
||||
};
|
||||
var submitFailCallback = function(){
|
||||
var submitFailCallback = function(data) {
|
||||
alert(
|
||||
"Error while submitting comment.\n" +
|
||||
"Error code {0} ({1}).".format(data.status, data.statusText)
|
||||
);
|
||||
self.resetCommentFormState(text);
|
||||
};
|
||||
self.submitAjaxPOST(
|
||||
|
|
@ -436,7 +446,11 @@ var bindToggleButtons = function() {
|
|||
$(self.previewContainer).show();
|
||||
|
||||
// by default we reset state of comment preserving the text
|
||||
var previewFailCallback = function(){
|
||||
var previewFailCallback = function(data){
|
||||
alert(
|
||||
"Error while preview of comment.\n" +
|
||||
"Error code {0} ({1}).".format(data.status, data.statusText)
|
||||
);
|
||||
self.resetCommentFormState(text)
|
||||
};
|
||||
self.submitAjaxPOST(
|
||||
|
|
@ -763,7 +777,11 @@ var CommentsController = function() {
|
|||
commentForm.setActionButtonsDisabled(false);
|
||||
|
||||
};
|
||||
var submitFailCallback = function(){
|
||||
var submitFailCallback = function(data){
|
||||
alert(
|
||||
"Error while submitting comment.\n" +
|
||||
"Error code {0} ({1}).".format(data.status, data.statusText)
|
||||
);
|
||||
commentForm.resetCommentFormState(text)
|
||||
};
|
||||
commentForm.submitAjaxPOST(
|
||||
|
|
|
|||
|
|
@ -378,49 +378,6 @@ var editPullRequest = function(repo_name, pull_request_id, title, description) {
|
|||
ajaxPOST(url, postData, success);
|
||||
};
|
||||
|
||||
var initPullRequestsCodeMirror = function (textAreaId) {
|
||||
var ta = $(textAreaId).get(0);
|
||||
var initialHeight = '100px';
|
||||
|
||||
// default options
|
||||
var codeMirrorOptions = {
|
||||
mode: "text",
|
||||
lineNumbers: false,
|
||||
indentUnit: 4,
|
||||
theme: 'rc-input'
|
||||
};
|
||||
|
||||
var codeMirrorInstance = CodeMirror.fromTextArea(ta, codeMirrorOptions);
|
||||
// marker for manually set description
|
||||
codeMirrorInstance._userDefinedDesc = false;
|
||||
codeMirrorInstance.setSize(null, initialHeight);
|
||||
codeMirrorInstance.on("change", function(instance, changeObj) {
|
||||
var height = initialHeight;
|
||||
var lines = instance.lineCount();
|
||||
if (lines > 6 && lines < 20) {
|
||||
height = "auto"
|
||||
}
|
||||
else if (lines >= 20) {
|
||||
height = 20 * 15;
|
||||
}
|
||||
instance.setSize(null, height);
|
||||
|
||||
// detect if the change was trigger by auto desc, or user input
|
||||
changeOrigin = changeObj.origin;
|
||||
|
||||
if (changeOrigin === "setValue") {
|
||||
cmLog.debug('Change triggered by setValue');
|
||||
}
|
||||
else {
|
||||
cmLog.debug('user triggered change !');
|
||||
// set special marker to indicate user has created an input.
|
||||
instance._userDefinedDesc = true;
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
return codeMirrorInstance
|
||||
};
|
||||
|
||||
/**
|
||||
* Reviewer autocomplete
|
||||
|
|
|
|||
|
|
@ -350,8 +350,7 @@
|
|||
</%def>
|
||||
|
||||
<%def name="pullrequest_title(title, description)">
|
||||
${title} <br/>
|
||||
${h.shorter(description, 40)}
|
||||
${title}
|
||||
</%def>
|
||||
|
||||
<%def name="pullrequest_comments(comments_nr)">
|
||||
|
|
@ -375,3 +374,52 @@
|
|||
<%def name="pullrequest_author(full_contact)">
|
||||
${base.gravatar_with_user(full_contact, 16)}
|
||||
</%def>
|
||||
|
||||
|
||||
<%def name="markup_form(form_id, form_text='', help_text=None)">
|
||||
|
||||
<div class="markup-form">
|
||||
<div class="markup-form-area">
|
||||
<div class="markup-form-area-header">
|
||||
<ul class="nav-links clearfix">
|
||||
<li class="active">
|
||||
<a href="#edit-text" tabindex="-1" id="edit-btn_${form_id}">${_('Write')}</a>
|
||||
</li>
|
||||
<li class="">
|
||||
<a href="#preview-text" tabindex="-1" id="preview-btn_${form_id}">${_('Preview')}</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="markup-form-area-write" style="display: block;">
|
||||
<div id="edit-container_${form_id}">
|
||||
<textarea id="${form_id}" name="${form_id}" class="comment-block-ta ac-input">${form_text if form_text else ''}</textarea>
|
||||
</div>
|
||||
<div id="preview-container_${form_id}" class="clearfix" style="display: none;">
|
||||
<div id="preview-box_${form_id}" class="preview-box"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="markup-form-area-footer">
|
||||
<div class="toolbar">
|
||||
<div class="toolbar-text">
|
||||
${(_('Parsed using %s syntax') % (
|
||||
('<a href="%s">%s</a>' % (h.route_url('%s_help' % c.visual.default_renderer), c.visual.default_renderer.upper())),
|
||||
)
|
||||
)|n}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="markup-form-footer">
|
||||
% if help_text:
|
||||
<span class="help-block">${help_text}</span>
|
||||
% endif
|
||||
</div>
|
||||
</div>
|
||||
<script type="text/javascript">
|
||||
new MarkupForm('${form_id}');
|
||||
</script>
|
||||
|
||||
</%def>
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
<%inherit file="/base/base.mako"/>
|
||||
<%namespace name="dt" file="/data_table/_dt_elements.mako"/>
|
||||
|
||||
<%def name="title()">
|
||||
${c.repo_name} ${_('New pull request')}
|
||||
|
|
@ -54,14 +55,13 @@
|
|||
<label for="pullrequest_desc">${_('Description')}:</label>
|
||||
</div>
|
||||
<div class="textarea text-area editor">
|
||||
${h.textarea('pullrequest_desc',size=30, )}
|
||||
<span class="help-block">${_('Write a short description on this pull request')}</span>
|
||||
${dt.markup_form('pullrequest_desc')}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="field">
|
||||
<div class="label label-textarea">
|
||||
<label for="pullrequest_desc">${_('Commit flow')}:</label>
|
||||
<label for="commit_flow">${_('Commit flow')}:</label>
|
||||
</div>
|
||||
|
||||
## TODO: johbo: Abusing the "content" class here to get the
|
||||
|
|
@ -164,379 +164,384 @@
|
|||
</div>
|
||||
|
||||
<script type="text/javascript">
|
||||
$(function(){
|
||||
var defaultSourceRepo = '${c.default_repo_data['source_repo_name']}';
|
||||
var defaultSourceRepoData = ${c.default_repo_data['source_refs_json']|n};
|
||||
var defaultTargetRepo = '${c.default_repo_data['target_repo_name']}';
|
||||
var defaultTargetRepoData = ${c.default_repo_data['target_refs_json']|n};
|
||||
|
||||
var $pullRequestForm = $('#pull_request_form');
|
||||
var $pullRequestSubmit = $('#pr_submit', $pullRequestForm);
|
||||
var $sourceRepo = $('#source_repo', $pullRequestForm);
|
||||
var $targetRepo = $('#target_repo', $pullRequestForm);
|
||||
var $sourceRef = $('#source_ref', $pullRequestForm);
|
||||
var $targetRef = $('#target_ref', $pullRequestForm);
|
||||
|
||||
var sourceRepo = function() { return $sourceRepo.eq(0).val() };
|
||||
var sourceRef = function() { return $sourceRef.eq(0).val().split(':') };
|
||||
|
||||
var targetRepo = function() { return $targetRepo.eq(0).val() };
|
||||
var targetRef = function() { return $targetRef.eq(0).val().split(':') };
|
||||
|
||||
var calculateContainerWidth = function() {
|
||||
var maxWidth = 0;
|
||||
var repoSelect2Containers = ['#source_repo', '#target_repo'];
|
||||
$.each(repoSelect2Containers, function(idx, value) {
|
||||
$(value).select2('container').width('auto');
|
||||
var curWidth = $(value).select2('container').width();
|
||||
if (maxWidth <= curWidth) {
|
||||
maxWidth = curWidth;
|
||||
}
|
||||
$.each(repoSelect2Containers, function(idx, value) {
|
||||
$(value).select2('container').width(maxWidth + 10);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
var initRefSelection = function(selectedRef) {
|
||||
return function(element, callback) {
|
||||
// translate our select2 id into a text, it's a mapping to show
|
||||
// simple label when selecting by internal ID.
|
||||
var id, refData;
|
||||
if (selectedRef === undefined || selectedRef === null) {
|
||||
id = element.val();
|
||||
refData = element.val().split(':');
|
||||
|
||||
if (refData.length !== 3){
|
||||
refData = ["", "", ""]
|
||||
}
|
||||
} else {
|
||||
id = selectedRef;
|
||||
refData = selectedRef.split(':');
|
||||
}
|
||||
|
||||
var text = refData[1];
|
||||
if (refData[0] === 'rev') {
|
||||
text = text.substring(0, 12);
|
||||
}
|
||||
|
||||
var data = {id: id, text: text};
|
||||
callback(data);
|
||||
};
|
||||
};
|
||||
|
||||
var formatRefSelection = function(item) {
|
||||
var prefix = '';
|
||||
var refData = item.id.split(':');
|
||||
if (refData[0] === 'branch') {
|
||||
prefix = '<i class="icon-branch"></i>';
|
||||
}
|
||||
else if (refData[0] === 'book') {
|
||||
prefix = '<i class="icon-bookmark"></i>';
|
||||
}
|
||||
else if (refData[0] === 'tag') {
|
||||
prefix = '<i class="icon-tag"></i>';
|
||||
}
|
||||
|
||||
var originalOption = item.element;
|
||||
return prefix + item.text;
|
||||
};
|
||||
|
||||
// custom code mirror
|
||||
var codeMirrorInstance = initPullRequestsCodeMirror('#pullrequest_desc');
|
||||
|
||||
reviewersController = new ReviewersController();
|
||||
|
||||
var queryTargetRepo = function(self, query) {
|
||||
// cache ALL results if query is empty
|
||||
var cacheKey = query.term || '__';
|
||||
var cachedData = self.cachedDataSource[cacheKey];
|
||||
|
||||
if (cachedData) {
|
||||
query.callback({results: cachedData.results});
|
||||
} else {
|
||||
$.ajax({
|
||||
url: pyroutes.url('pullrequest_repo_destinations', {'repo_name': templateContext.repo_name}),
|
||||
data: {query: query.term},
|
||||
dataType: 'json',
|
||||
type: 'GET',
|
||||
success: function(data) {
|
||||
self.cachedDataSource[cacheKey] = data;
|
||||
query.callback({results: data.results});
|
||||
},
|
||||
error: function(data, textStatus, errorThrown) {
|
||||
alert(
|
||||
"Error while fetching entries.\nError code {0} ({1}).".format(data.status, data.statusText));
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
var queryTargetRefs = function(initialData, query) {
|
||||
var data = {results: []};
|
||||
// filter initialData
|
||||
$.each(initialData, function() {
|
||||
var section = this.text;
|
||||
var children = [];
|
||||
$.each(this.children, function() {
|
||||
if (query.term.length === 0 ||
|
||||
this.text.toUpperCase().indexOf(query.term.toUpperCase()) >= 0 ) {
|
||||
children.push({'id': this.id, 'text': this.text})
|
||||
}
|
||||
});
|
||||
data.results.push({'text': section, 'children': children})
|
||||
});
|
||||
query.callback({results: data.results});
|
||||
};
|
||||
|
||||
var loadRepoRefDiffPreview = function() {
|
||||
|
||||
var url_data = {
|
||||
'repo_name': targetRepo(),
|
||||
'target_repo': sourceRepo(),
|
||||
'source_ref': targetRef()[2],
|
||||
'source_ref_type': 'rev',
|
||||
'target_ref': sourceRef()[2],
|
||||
'target_ref_type': 'rev',
|
||||
'merge': true,
|
||||
'_': Date.now() // bypass browser caching
|
||||
}; // gather the source/target ref and repo here
|
||||
|
||||
if (sourceRef().length !== 3 || targetRef().length !== 3) {
|
||||
prButtonLock(true, "${_('Please select source and target')}");
|
||||
return;
|
||||
}
|
||||
var url = pyroutes.url('repo_compare', url_data);
|
||||
|
||||
// lock PR button, so we cannot send PR before it's calculated
|
||||
prButtonLock(true, "${_('Loading compare ...')}", 'compare');
|
||||
|
||||
if (loadRepoRefDiffPreview._currentRequest) {
|
||||
loadRepoRefDiffPreview._currentRequest.abort();
|
||||
}
|
||||
|
||||
loadRepoRefDiffPreview._currentRequest = $.get(url)
|
||||
.error(function(data, textStatus, errorThrown) {
|
||||
if (textStatus !== 'abort') {
|
||||
alert(
|
||||
"Error while processing request.\nError code {0} ({1}).".format(
|
||||
data.status, data.statusText));
|
||||
}
|
||||
|
||||
})
|
||||
.done(function(data) {
|
||||
loadRepoRefDiffPreview._currentRequest = null;
|
||||
$('#pull_request_overview').html(data);
|
||||
|
||||
var commitElements = $(data).find('tr[commit_id]');
|
||||
|
||||
var prTitleAndDesc = getTitleAndDescription(
|
||||
sourceRef()[1], commitElements, 5);
|
||||
|
||||
var title = prTitleAndDesc[0];
|
||||
var proposedDescription = prTitleAndDesc[1];
|
||||
|
||||
var useGeneratedTitle = (
|
||||
$('#pullrequest_title').hasClass('autogenerated-title') ||
|
||||
$('#pullrequest_title').val() === "");
|
||||
|
||||
if (title && useGeneratedTitle) {
|
||||
// use generated title if we haven't specified our own
|
||||
$('#pullrequest_title').val(title);
|
||||
$('#pullrequest_title').addClass('autogenerated-title');
|
||||
|
||||
}
|
||||
|
||||
var useGeneratedDescription = (
|
||||
!codeMirrorInstance._userDefinedDesc ||
|
||||
codeMirrorInstance.getValue() === "");
|
||||
|
||||
if (proposedDescription && useGeneratedDescription) {
|
||||
// set proposed content, if we haven't defined our own,
|
||||
// or we don't have description written
|
||||
codeMirrorInstance._userDefinedDesc = false; // reset state
|
||||
codeMirrorInstance.setValue(proposedDescription);
|
||||
}
|
||||
|
||||
var msg = '';
|
||||
if (commitElements.length === 1) {
|
||||
msg = "${_ungettext('This pull request will consist of __COMMITS__ commit.', 'This pull request will consist of __COMMITS__ commits.', 1)}";
|
||||
} else {
|
||||
msg = "${_ungettext('This pull request will consist of __COMMITS__ commit.', 'This pull request will consist of __COMMITS__ commits.', 2)}";
|
||||
}
|
||||
|
||||
msg += ' <a id="pull_request_overview_url" href="{0}" target="_blank">${_('Show detailed compare.')}</a>'.format(url);
|
||||
|
||||
if (commitElements.length) {
|
||||
var commitsLink = '<a href="#pull_request_overview"><strong>{0}</strong></a>'.format(commitElements.length);
|
||||
prButtonLock(false, msg.replace('__COMMITS__', commitsLink), 'compare');
|
||||
}
|
||||
else {
|
||||
prButtonLock(true, "${_('There are no commits to merge.')}", 'compare');
|
||||
}
|
||||
|
||||
|
||||
});
|
||||
};
|
||||
|
||||
var Select2Box = function(element, overrides) {
|
||||
var globalDefaults = {
|
||||
dropdownAutoWidth: true,
|
||||
containerCssClass: "drop-menu",
|
||||
dropdownCssClass: "drop-menu-dropdown"
|
||||
};
|
||||
|
||||
var initSelect2 = function(defaultOptions) {
|
||||
var options = jQuery.extend(globalDefaults, defaultOptions, overrides);
|
||||
element.select2(options);
|
||||
};
|
||||
|
||||
return {
|
||||
initRef: function() {
|
||||
var defaultOptions = {
|
||||
minimumResultsForSearch: 5,
|
||||
formatSelection: formatRefSelection
|
||||
};
|
||||
|
||||
initSelect2(defaultOptions);
|
||||
},
|
||||
|
||||
initRepo: function(defaultValue, readOnly) {
|
||||
var defaultOptions = {
|
||||
initSelection : function (element, callback) {
|
||||
var data = {id: defaultValue, text: defaultValue};
|
||||
callback(data);
|
||||
}
|
||||
};
|
||||
|
||||
initSelect2(defaultOptions);
|
||||
|
||||
element.select2('val', defaultSourceRepo);
|
||||
if (readOnly === true) {
|
||||
element.select2('readonly', true);
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
var initTargetRefs = function(refsData, selectedRef) {
|
||||
|
||||
Select2Box($targetRef, {
|
||||
placeholder: "${_('Select commit reference')}",
|
||||
query: function(query) {
|
||||
queryTargetRefs(refsData, query);
|
||||
},
|
||||
initSelection : initRefSelection(selectedRef)
|
||||
}).initRef();
|
||||
|
||||
if (!(selectedRef === undefined)) {
|
||||
$targetRef.select2('val', selectedRef);
|
||||
}
|
||||
};
|
||||
|
||||
var targetRepoChanged = function(repoData) {
|
||||
// generate new DESC of target repo displayed next to select
|
||||
var prLink = pyroutes.url('pullrequest_new', {'repo_name': repoData['name']});
|
||||
$('#target_repo_desc').html(
|
||||
"<strong>${_('Target repository')}</strong>: {0}. <a href=\"{1}\">Switch base, and use as source.</a>".format(repoData['description'], prLink)
|
||||
);
|
||||
|
||||
// generate dynamic select2 for refs.
|
||||
initTargetRefs(repoData['refs']['select2_refs'],
|
||||
repoData['refs']['selected_ref']);
|
||||
|
||||
};
|
||||
|
||||
var sourceRefSelect2 = Select2Box($sourceRef, {
|
||||
placeholder: "${_('Select commit reference')}",
|
||||
query: function(query) {
|
||||
var initialData = defaultSourceRepoData['refs']['select2_refs'];
|
||||
queryTargetRefs(initialData, query)
|
||||
},
|
||||
initSelection: initRefSelection()
|
||||
}
|
||||
);
|
||||
|
||||
var sourceRepoSelect2 = Select2Box($sourceRepo, {
|
||||
query: function(query) {}
|
||||
});
|
||||
|
||||
var targetRepoSelect2 = Select2Box($targetRepo, {
|
||||
cachedDataSource: {},
|
||||
query: $.debounce(250, function(query) {
|
||||
queryTargetRepo(this, query);
|
||||
}),
|
||||
formatResult: formatRepoResult
|
||||
});
|
||||
|
||||
sourceRefSelect2.initRef();
|
||||
|
||||
sourceRepoSelect2.initRepo(defaultSourceRepo, true);
|
||||
|
||||
targetRepoSelect2.initRepo(defaultTargetRepo, false);
|
||||
|
||||
$sourceRef.on('change', function(e){
|
||||
loadRepoRefDiffPreview();
|
||||
reviewersController.loadDefaultReviewers(
|
||||
sourceRepo(), sourceRef(), targetRepo(), targetRef());
|
||||
});
|
||||
|
||||
$targetRef.on('change', function(e){
|
||||
loadRepoRefDiffPreview();
|
||||
reviewersController.loadDefaultReviewers(
|
||||
sourceRepo(), sourceRef(), targetRepo(), targetRef());
|
||||
});
|
||||
|
||||
$targetRepo.on('change', function(e){
|
||||
var repoName = $(this).val();
|
||||
calculateContainerWidth();
|
||||
$targetRef.select2('destroy');
|
||||
$('#target_ref_loading').show();
|
||||
|
||||
$.ajax({
|
||||
url: pyroutes.url('pullrequest_repo_refs',
|
||||
{'repo_name': templateContext.repo_name, 'target_repo_name':repoName}),
|
||||
data: {},
|
||||
dataType: 'json',
|
||||
type: 'GET',
|
||||
success: function(data) {
|
||||
$('#target_ref_loading').hide();
|
||||
targetRepoChanged(data);
|
||||
loadRepoRefDiffPreview();
|
||||
},
|
||||
error: function(data, textStatus, errorThrown) {
|
||||
alert("Error while fetching entries.\nError code {0} ({1}).".format(data.status, data.statusText));
|
||||
}
|
||||
})
|
||||
|
||||
});
|
||||
|
||||
$pullRequestForm.on('submit', function(e){
|
||||
prButtonLock(true, null, 'all');
|
||||
});
|
||||
|
||||
prButtonLock(true, "${_('Please select source and target')}", 'all');
|
||||
|
||||
// auto-load on init, the target refs select2
|
||||
calculateContainerWidth();
|
||||
targetRepoChanged(defaultTargetRepoData);
|
||||
|
||||
$('#pullrequest_title').on('keyup', function(e){
|
||||
$(this).removeClass('autogenerated-title');
|
||||
});
|
||||
|
||||
% if c.default_source_ref:
|
||||
// in case we have a pre-selected value, use it now
|
||||
$sourceRef.select2('val', '${c.default_source_ref}');
|
||||
// diff preview load
|
||||
loadRepoRefDiffPreview();
|
||||
// default reviewers
|
||||
reviewersController.loadDefaultReviewers(
|
||||
sourceRepo(), sourceRef(), targetRepo(), targetRef());
|
||||
% endif
|
||||
|
||||
ReviewerAutoComplete('#user');
|
||||
});
|
||||
$(function(){
|
||||
var defaultSourceRepo = '${c.default_repo_data['source_repo_name']}';
|
||||
var defaultSourceRepoData = ${c.default_repo_data['source_refs_json']|n};
|
||||
var defaultTargetRepo = '${c.default_repo_data['target_repo_name']}';
|
||||
var defaultTargetRepoData = ${c.default_repo_data['target_refs_json']|n};
|
||||
|
||||
var $pullRequestForm = $('#pull_request_form');
|
||||
var $pullRequestSubmit = $('#pr_submit', $pullRequestForm);
|
||||
var $sourceRepo = $('#source_repo', $pullRequestForm);
|
||||
var $targetRepo = $('#target_repo', $pullRequestForm);
|
||||
var $sourceRef = $('#source_ref', $pullRequestForm);
|
||||
var $targetRef = $('#target_ref', $pullRequestForm);
|
||||
|
||||
var sourceRepo = function() { return $sourceRepo.eq(0).val() };
|
||||
var sourceRef = function() { return $sourceRef.eq(0).val().split(':') };
|
||||
|
||||
var targetRepo = function() { return $targetRepo.eq(0).val() };
|
||||
var targetRef = function() { return $targetRef.eq(0).val().split(':') };
|
||||
|
||||
var calculateContainerWidth = function() {
|
||||
var maxWidth = 0;
|
||||
var repoSelect2Containers = ['#source_repo', '#target_repo'];
|
||||
$.each(repoSelect2Containers, function(idx, value) {
|
||||
$(value).select2('container').width('auto');
|
||||
var curWidth = $(value).select2('container').width();
|
||||
if (maxWidth <= curWidth) {
|
||||
maxWidth = curWidth;
|
||||
}
|
||||
$.each(repoSelect2Containers, function(idx, value) {
|
||||
$(value).select2('container').width(maxWidth + 10);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
var initRefSelection = function(selectedRef) {
|
||||
return function(element, callback) {
|
||||
// translate our select2 id into a text, it's a mapping to show
|
||||
// simple label when selecting by internal ID.
|
||||
var id, refData;
|
||||
if (selectedRef === undefined || selectedRef === null) {
|
||||
id = element.val();
|
||||
refData = element.val().split(':');
|
||||
|
||||
if (refData.length !== 3){
|
||||
refData = ["", "", ""]
|
||||
}
|
||||
} else {
|
||||
id = selectedRef;
|
||||
refData = selectedRef.split(':');
|
||||
}
|
||||
|
||||
var text = refData[1];
|
||||
if (refData[0] === 'rev') {
|
||||
text = text.substring(0, 12);
|
||||
}
|
||||
|
||||
var data = {id: id, text: text};
|
||||
callback(data);
|
||||
};
|
||||
};
|
||||
|
||||
var formatRefSelection = function(item) {
|
||||
var prefix = '';
|
||||
var refData = item.id.split(':');
|
||||
if (refData[0] === 'branch') {
|
||||
prefix = '<i class="icon-branch"></i>';
|
||||
}
|
||||
else if (refData[0] === 'book') {
|
||||
prefix = '<i class="icon-bookmark"></i>';
|
||||
}
|
||||
else if (refData[0] === 'tag') {
|
||||
prefix = '<i class="icon-tag"></i>';
|
||||
}
|
||||
|
||||
var originalOption = item.element;
|
||||
return prefix + item.text;
|
||||
};
|
||||
|
||||
// custom code mirror
|
||||
var codeMirrorInstance = $('#pullrequest_desc').get(0).MarkupForm.cm;
|
||||
|
||||
reviewersController = new ReviewersController();
|
||||
|
||||
var queryTargetRepo = function(self, query) {
|
||||
// cache ALL results if query is empty
|
||||
var cacheKey = query.term || '__';
|
||||
var cachedData = self.cachedDataSource[cacheKey];
|
||||
|
||||
if (cachedData) {
|
||||
query.callback({results: cachedData.results});
|
||||
} else {
|
||||
$.ajax({
|
||||
url: pyroutes.url('pullrequest_repo_destinations', {'repo_name': templateContext.repo_name}),
|
||||
data: {query: query.term},
|
||||
dataType: 'json',
|
||||
type: 'GET',
|
||||
success: function(data) {
|
||||
self.cachedDataSource[cacheKey] = data;
|
||||
query.callback({results: data.results});
|
||||
},
|
||||
error: function(data, textStatus, errorThrown) {
|
||||
alert(
|
||||
"Error while fetching entries.\nError code {0} ({1}).".format(data.status, data.statusText));
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
var queryTargetRefs = function(initialData, query) {
|
||||
var data = {results: []};
|
||||
// filter initialData
|
||||
$.each(initialData, function() {
|
||||
var section = this.text;
|
||||
var children = [];
|
||||
$.each(this.children, function() {
|
||||
if (query.term.length === 0 ||
|
||||
this.text.toUpperCase().indexOf(query.term.toUpperCase()) >= 0 ) {
|
||||
children.push({'id': this.id, 'text': this.text})
|
||||
}
|
||||
});
|
||||
data.results.push({'text': section, 'children': children})
|
||||
});
|
||||
query.callback({results: data.results});
|
||||
};
|
||||
|
||||
var loadRepoRefDiffPreview = function() {
|
||||
|
||||
var url_data = {
|
||||
'repo_name': targetRepo(),
|
||||
'target_repo': sourceRepo(),
|
||||
'source_ref': targetRef()[2],
|
||||
'source_ref_type': 'rev',
|
||||
'target_ref': sourceRef()[2],
|
||||
'target_ref_type': 'rev',
|
||||
'merge': true,
|
||||
'_': Date.now() // bypass browser caching
|
||||
}; // gather the source/target ref and repo here
|
||||
|
||||
if (sourceRef().length !== 3 || targetRef().length !== 3) {
|
||||
prButtonLock(true, "${_('Please select source and target')}");
|
||||
return;
|
||||
}
|
||||
var url = pyroutes.url('repo_compare', url_data);
|
||||
|
||||
// lock PR button, so we cannot send PR before it's calculated
|
||||
prButtonLock(true, "${_('Loading compare ...')}", 'compare');
|
||||
|
||||
if (loadRepoRefDiffPreview._currentRequest) {
|
||||
loadRepoRefDiffPreview._currentRequest.abort();
|
||||
}
|
||||
|
||||
loadRepoRefDiffPreview._currentRequest = $.get(url)
|
||||
.error(function(data, textStatus, errorThrown) {
|
||||
if (textStatus !== 'abort') {
|
||||
alert(
|
||||
"Error while processing request.\nError code {0} ({1}).".format(
|
||||
data.status, data.statusText));
|
||||
}
|
||||
|
||||
})
|
||||
.done(function(data) {
|
||||
loadRepoRefDiffPreview._currentRequest = null;
|
||||
$('#pull_request_overview').html(data);
|
||||
|
||||
var commitElements = $(data).find('tr[commit_id]');
|
||||
|
||||
var prTitleAndDesc = getTitleAndDescription(
|
||||
sourceRef()[1], commitElements, 5);
|
||||
|
||||
var title = prTitleAndDesc[0];
|
||||
var proposedDescription = prTitleAndDesc[1];
|
||||
|
||||
var useGeneratedTitle = (
|
||||
$('#pullrequest_title').hasClass('autogenerated-title') ||
|
||||
$('#pullrequest_title').val() === "");
|
||||
|
||||
if (title && useGeneratedTitle) {
|
||||
// use generated title if we haven't specified our own
|
||||
$('#pullrequest_title').val(title);
|
||||
$('#pullrequest_title').addClass('autogenerated-title');
|
||||
|
||||
}
|
||||
|
||||
var useGeneratedDescription = (
|
||||
!codeMirrorInstance._userDefinedValue ||
|
||||
codeMirrorInstance.getValue() === "");
|
||||
|
||||
if (proposedDescription && useGeneratedDescription) {
|
||||
// set proposed content, if we haven't defined our own,
|
||||
// or we don't have description written
|
||||
codeMirrorInstance._userDefinedValue = false; // reset state
|
||||
codeMirrorInstance.setValue(proposedDescription);
|
||||
}
|
||||
|
||||
// refresh our codeMirror so events kicks in and it's change aware
|
||||
codeMirrorInstance.refresh();
|
||||
|
||||
var msg = '';
|
||||
if (commitElements.length === 1) {
|
||||
msg = "${_ungettext('This pull request will consist of __COMMITS__ commit.', 'This pull request will consist of __COMMITS__ commits.', 1)}";
|
||||
} else {
|
||||
msg = "${_ungettext('This pull request will consist of __COMMITS__ commit.', 'This pull request will consist of __COMMITS__ commits.', 2)}";
|
||||
}
|
||||
|
||||
msg += ' <a id="pull_request_overview_url" href="{0}" target="_blank">${_('Show detailed compare.')}</a>'.format(url);
|
||||
|
||||
if (commitElements.length) {
|
||||
var commitsLink = '<a href="#pull_request_overview"><strong>{0}</strong></a>'.format(commitElements.length);
|
||||
prButtonLock(false, msg.replace('__COMMITS__', commitsLink), 'compare');
|
||||
}
|
||||
else {
|
||||
prButtonLock(true, "${_('There are no commits to merge.')}", 'compare');
|
||||
}
|
||||
|
||||
|
||||
});
|
||||
};
|
||||
|
||||
var Select2Box = function(element, overrides) {
|
||||
var globalDefaults = {
|
||||
dropdownAutoWidth: true,
|
||||
containerCssClass: "drop-menu",
|
||||
dropdownCssClass: "drop-menu-dropdown"
|
||||
};
|
||||
|
||||
var initSelect2 = function(defaultOptions) {
|
||||
var options = jQuery.extend(globalDefaults, defaultOptions, overrides);
|
||||
element.select2(options);
|
||||
};
|
||||
|
||||
return {
|
||||
initRef: function() {
|
||||
var defaultOptions = {
|
||||
minimumResultsForSearch: 5,
|
||||
formatSelection: formatRefSelection
|
||||
};
|
||||
|
||||
initSelect2(defaultOptions);
|
||||
},
|
||||
|
||||
initRepo: function(defaultValue, readOnly) {
|
||||
var defaultOptions = {
|
||||
initSelection : function (element, callback) {
|
||||
var data = {id: defaultValue, text: defaultValue};
|
||||
callback(data);
|
||||
}
|
||||
};
|
||||
|
||||
initSelect2(defaultOptions);
|
||||
|
||||
element.select2('val', defaultSourceRepo);
|
||||
if (readOnly === true) {
|
||||
element.select2('readonly', true);
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
var initTargetRefs = function(refsData, selectedRef) {
|
||||
|
||||
Select2Box($targetRef, {
|
||||
placeholder: "${_('Select commit reference')}",
|
||||
query: function(query) {
|
||||
queryTargetRefs(refsData, query);
|
||||
},
|
||||
initSelection : initRefSelection(selectedRef)
|
||||
}).initRef();
|
||||
|
||||
if (!(selectedRef === undefined)) {
|
||||
$targetRef.select2('val', selectedRef);
|
||||
}
|
||||
};
|
||||
|
||||
var targetRepoChanged = function(repoData) {
|
||||
// generate new DESC of target repo displayed next to select
|
||||
var prLink = pyroutes.url('pullrequest_new', {'repo_name': repoData['name']});
|
||||
$('#target_repo_desc').html(
|
||||
"<strong>${_('Target repository')}</strong>: {0}. <a href=\"{1}\">Switch base, and use as source.</a>".format(repoData['description'], prLink)
|
||||
);
|
||||
|
||||
// generate dynamic select2 for refs.
|
||||
initTargetRefs(repoData['refs']['select2_refs'],
|
||||
repoData['refs']['selected_ref']);
|
||||
|
||||
};
|
||||
|
||||
var sourceRefSelect2 = Select2Box($sourceRef, {
|
||||
placeholder: "${_('Select commit reference')}",
|
||||
query: function(query) {
|
||||
var initialData = defaultSourceRepoData['refs']['select2_refs'];
|
||||
queryTargetRefs(initialData, query)
|
||||
},
|
||||
initSelection: initRefSelection()
|
||||
}
|
||||
);
|
||||
|
||||
var sourceRepoSelect2 = Select2Box($sourceRepo, {
|
||||
query: function(query) {}
|
||||
});
|
||||
|
||||
var targetRepoSelect2 = Select2Box($targetRepo, {
|
||||
cachedDataSource: {},
|
||||
query: $.debounce(250, function(query) {
|
||||
queryTargetRepo(this, query);
|
||||
}),
|
||||
formatResult: formatRepoResult
|
||||
});
|
||||
|
||||
sourceRefSelect2.initRef();
|
||||
|
||||
sourceRepoSelect2.initRepo(defaultSourceRepo, true);
|
||||
|
||||
targetRepoSelect2.initRepo(defaultTargetRepo, false);
|
||||
|
||||
$sourceRef.on('change', function(e){
|
||||
loadRepoRefDiffPreview();
|
||||
reviewersController.loadDefaultReviewers(
|
||||
sourceRepo(), sourceRef(), targetRepo(), targetRef());
|
||||
});
|
||||
|
||||
$targetRef.on('change', function(e){
|
||||
loadRepoRefDiffPreview();
|
||||
reviewersController.loadDefaultReviewers(
|
||||
sourceRepo(), sourceRef(), targetRepo(), targetRef());
|
||||
});
|
||||
|
||||
$targetRepo.on('change', function(e){
|
||||
var repoName = $(this).val();
|
||||
calculateContainerWidth();
|
||||
$targetRef.select2('destroy');
|
||||
$('#target_ref_loading').show();
|
||||
|
||||
$.ajax({
|
||||
url: pyroutes.url('pullrequest_repo_refs',
|
||||
{'repo_name': templateContext.repo_name, 'target_repo_name':repoName}),
|
||||
data: {},
|
||||
dataType: 'json',
|
||||
type: 'GET',
|
||||
success: function(data) {
|
||||
$('#target_ref_loading').hide();
|
||||
targetRepoChanged(data);
|
||||
loadRepoRefDiffPreview();
|
||||
},
|
||||
error: function(data, textStatus, errorThrown) {
|
||||
alert("Error while fetching entries.\nError code {0} ({1}).".format(data.status, data.statusText));
|
||||
}
|
||||
})
|
||||
|
||||
});
|
||||
|
||||
$pullRequestForm.on('submit', function(e){
|
||||
// Flush changes into textarea
|
||||
codeMirrorInstance.save();
|
||||
prButtonLock(true, null, 'all');
|
||||
});
|
||||
|
||||
prButtonLock(true, "${_('Please select source and target')}", 'all');
|
||||
|
||||
// auto-load on init, the target refs select2
|
||||
calculateContainerWidth();
|
||||
targetRepoChanged(defaultTargetRepoData);
|
||||
|
||||
$('#pullrequest_title').on('keyup', function(e){
|
||||
$(this).removeClass('autogenerated-title');
|
||||
});
|
||||
|
||||
% if c.default_source_ref:
|
||||
// in case we have a pre-selected value, use it now
|
||||
$sourceRef.select2('val', '${c.default_source_ref}');
|
||||
// diff preview load
|
||||
loadRepoRefDiffPreview();
|
||||
// default reviewers
|
||||
reviewersController.loadDefaultReviewers(
|
||||
sourceRepo(), sourceRef(), targetRepo(), targetRef());
|
||||
% endif
|
||||
|
||||
ReviewerAutoComplete('#user');
|
||||
});
|
||||
</script>
|
||||
|
||||
</%def>
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
<%inherit file="/base/base.mako"/>
|
||||
<%namespace name="base" file="/base/base.mako"/>
|
||||
<%namespace name="dt" file="/data_table/_dt_elements.mako"/>
|
||||
|
||||
<%def name="title()">
|
||||
${_('%s Pull Request #%s') % (c.repo_name, c.pull_request.pull_request_id)}
|
||||
|
|
@ -169,10 +170,10 @@
|
|||
<label>${_('Description')}:</label>
|
||||
</div>
|
||||
<div id="pr-desc" class="input">
|
||||
<div class="pr-description">${h.urlify_commit_message(c.pull_request.description, c.repo_name)}</div>
|
||||
<div class="pr-description">${h.render(c.pull_request.description, renderer=c.visual.default_renderer)}</div>
|
||||
</div>
|
||||
<div id="pr-desc-edit" class="input textarea editor" style="display: none;">
|
||||
<textarea id="pr-description-input" size="30">${c.pull_request.description}</textarea>
|
||||
${dt.markup_form('pr-description-input', form_text=c.pull_request.description)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
@ -643,7 +644,7 @@
|
|||
$(function(){
|
||||
|
||||
// custom code mirror
|
||||
var codeMirrorInstance = initPullRequestsCodeMirror('#pr-description-input');
|
||||
var codeMirrorInstance = $('#pr-description-input').get(0).MarkupForm.cm;
|
||||
|
||||
var PRDetails = {
|
||||
editButton: $('#open_edit_pullrequest'),
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue