Skip to content
Snippets Groups Projects
Commit 83874edb authored by Grzegorz Bizon's avatar Grzegorz Bizon
Browse files

Merge branch 'master' into 'update-kubeclient'

 Conflicts:
   Gemfile.lock
parents 53e2987b ea5221ae
No related branches found
No related tags found
No related merge requests found
Showing
with 1155 additions and 788 deletions
Loading
Loading
@@ -35,15 +35,22 @@
"import/no-commonjs": "error",
"no-multiple-empty-lines": ["error", { "max": 1 }],
"promise/catch-or-return": "error",
"no-underscore-dangle": ["error", { "allow": ["__", "_links"]}],
"vue/html-self-closing": ["error", {
"html": {
"void": "always",
"normal": "never",
"component": "always"
},
"svg": "always",
"math": "always"
}]
"no-underscore-dangle": ["error", { "allow": ["__", "_links"] }],
"no-mixed-operators": 0,
"space-before-function-paren": 0,
"curly": 0,
"arrow-parens": 0,
"vue/html-self-closing": [
"error",
{
"html": {
"void": "always",
"normal": "never",
"component": "always"
},
"svg": "always",
"math": "always"
}
]
}
}
Loading
Loading
@@ -11,6 +11,7 @@
eslint-report.html
/.gitlab_shell_secret
.idea
/.vscode/*
/.rbenv-version
.rbx/
/.ruby-gemset
Loading
Loading
Loading
Loading
@@ -257,7 +257,7 @@ stages:
##
# Trigger a package build in omnibus-gitlab repository
#
package-qa:
package-and-qa:
<<: *dedicated-runner
image: ruby:2.4-alpine
before_script: []
Loading
Loading
{
"singleQuote": true,
"trailingComma": "all"
}
Loading
Loading
@@ -2,6 +2,20 @@
documentation](doc/development/changelog.md) for instructions on adding your own
entry.
 
## 10.5.5 (2018-03-15)
### Fixed (3 changes)
- Fix missing uploads after group transfer. !17658
- Fix code and wiki search results when filename is non-ASCII.
- Remove double caching of Repository#empty?.
### Performance (2 changes)
- Adding missing indexes on taggings table.
- Add index on section_name_id on ci_build_trace_sections table.
## 10.5.4 (2018-03-08)
 
### Fixed (11 changes)
Loading
Loading
0.89.0
0.91.0
0.7.0
0.7.1
Loading
Loading
@@ -34,7 +34,7 @@ gem 'omniauth-gitlab', '~> 1.0.2'
gem 'omniauth-google-oauth2', '~> 0.5.2'
gem 'omniauth-kerberos', '~> 0.3.0', group: :kerberos
gem 'omniauth-oauth2-generic', '~> 0.2.2'
gem 'omniauth-saml', '~> 1.7.0'
gem 'omniauth-saml', '~> 1.10.0'
gem 'omniauth-shibboleth', '~> 1.2.0'
gem 'omniauth-twitter', '~> 1.2.0'
gem 'omniauth_crowd', '~> 2.2.0'
Loading
Loading
@@ -104,7 +104,7 @@ gem 'carrierwave', '~> 1.2'
gem 'dropzonejs-rails', '~> 0.7.1'
 
# for backups
gem 'fog-aws', '~> 1.4'
gem 'fog-aws', '~> 2.0'
gem 'fog-core', '~> 1.44'
gem 'fog-google', '~> 0.5'
gem 'fog-local', '~> 0.3'
Loading
Loading
@@ -152,7 +152,7 @@ end
gem 'state_machines-activerecord', '~> 0.4.0'
 
# Issue tags
gem 'acts-as-taggable-on', '~> 4.0'
gem 'acts-as-taggable-on', '~> 5.0'
 
# Background jobs
gem 'sidekiq', '~> 5.0'
Loading
Loading
@@ -257,14 +257,13 @@ gem 'font-awesome-rails', '~> 4.7'
gem 'gemojione', '~> 3.3'
gem 'gon', '~> 6.1.0'
gem 'jquery-atwho-rails', '~> 1.3.2'
gem 'jquery-rails', '~> 4.3.1'
gem 'request_store', '~> 1.3'
gem 'select2-rails', '~> 3.5.9'
gem 'virtus', '~> 1.0.1'
gem 'base32', '~> 0.3.0'
 
# Sentry integration
gem 'sentry-raven', '~> 2.5.3'
gem 'sentry-raven', '~> 2.7'
 
gem 'premailer-rails', '~> 1.9.7'
 
Loading
Loading
@@ -300,7 +299,7 @@ group :metrics do
end
 
group :development do
gem 'foreman', '~> 0.78.0'
gem 'foreman', '~> 0.84.0'
gem 'brakeman', '~> 3.6.0', require: false
 
gem 'letter_opener_web', '~> 1.3.0'
Loading
Loading
@@ -360,7 +359,7 @@ group :development, :test do
gem 'benchmark-ips', '~> 2.3.0', require: false
 
gem 'license_finder', '~> 3.1', require: false
gem 'knapsack', '~> 1.11.0'
gem 'knapsack', '~> 1.16'
 
gem 'activerecord_sane_schema_dumper', '0.2'
 
Loading
Loading
@@ -381,14 +380,14 @@ group :test do
gem 'test-prof', '~> 0.2.5'
end
 
gem 'octokit', '~> 4.6.2'
gem 'octokit', '~> 4.8'
 
gem 'mail_room', '~> 0.9.1'
 
gem 'email_reply_trimmer', '~> 0.1'
gem 'html2text'
 
gem 'ruby-prof', '~> 0.16.2'
gem 'ruby-prof', '~> 0.17.0'
 
# OAuth
gem 'oauth2', '~> 1.4'
Loading
Loading
@@ -401,7 +400,7 @@ gem 'vmstat', '~> 2.3.0'
gem 'sys-filesystem', '~> 1.1.6'
 
# SSH host key support
gem 'net-ssh', '~> 4.1.0'
gem 'net-ssh', '~> 4.2.0'
gem 'sshkey', '~> 1.9.0'
 
# Required for ED25519 SSH host key support
Loading
Loading
@@ -421,9 +420,9 @@ gem 'google-protobuf', '= 3.5.1'
gem 'toml-rb', '~> 1.0.0', require: false
 
# Feature toggles
gem 'flipper', '~> 0.11.0'
gem 'flipper-active_record', '~> 0.11.0'
gem 'flipper-active_support_cache_store', '~> 0.11.0'
gem 'flipper', '~> 0.13.0'
gem 'flipper-active_record', '~> 0.13.0'
gem 'flipper-active_support_cache_store', '~> 0.13.0'
 
# Structured logging
gem 'lograge', '~> 0.5'
Loading
Loading
Loading
Loading
@@ -40,8 +40,8 @@ GEM
minitest (~> 5.1)
thread_safe (~> 0.3, >= 0.3.4)
tzinfo (~> 1.1)
acts-as-taggable-on (4.0.0)
activerecord (>= 4.0)
acts-as-taggable-on (5.0.0)
activerecord (>= 4.2.8)
adamantium (0.2.0)
ice_nine (~> 0.11.0)
memoizable (~> 0.4.0)
Loading
Loading
@@ -194,7 +194,7 @@ GEM
et-orbi (1.0.3)
tzinfo
eventmachine (1.0.8)
excon (0.57.1)
excon (0.60.0)
execjs (2.6.0)
expression_parser (0.9.0)
factory_bot (4.8.2)
Loading
Loading
@@ -218,13 +218,13 @@ GEM
path_expander (~> 1.0)
ruby_parser (~> 3.0)
sexp_processor (~> 4.0)
flipper (0.11.0)
flipper-active_record (0.11.0)
flipper (0.13.0)
flipper-active_record (0.13.0)
activerecord (>= 3.2, < 6)
flipper (~> 0.11.0)
flipper-active_support_cache_store (0.11.0)
flipper (~> 0.13.0)
flipper-active_support_cache_store (0.13.0)
activesupport (>= 3.2, < 6)
flipper (~> 0.11.0)
flipper (~> 0.13.0)
flowdock (0.7.1)
httparty (~> 0.7)
multi_json
Loading
Loading
@@ -233,14 +233,14 @@ GEM
fog-json (~> 1.0)
ipaddress (~> 0.8)
xml-simple (~> 1.1)
fog-aws (1.4.0)
fog-aws (2.0.1)
fog-core (~> 1.38)
fog-json (~> 1.0)
fog-xml (~> 0.1)
ipaddress (~> 0.8)
fog-core (1.44.3)
fog-core (1.45.0)
builder
excon (~> 0.49)
excon (~> 0.58)
formatador (~> 0.2)
fog-google (0.5.3)
fog-core
Loading
Loading
@@ -265,7 +265,7 @@ GEM
nokogiri (>= 1.5.11, < 2.0.0)
font-awesome-rails (4.7.0.1)
railties (>= 3.2, < 5.1)
foreman (0.78.0)
foreman (0.84.0)
thor (~> 0.19.1)
formatador (0.2.5)
fuubar (2.2.0)
Loading
Loading
@@ -388,7 +388,7 @@ GEM
thor
tilt
hashdiff (0.3.4)
hashie (3.5.6)
hashie (3.5.7)
hashie-forbidden_attributes (0.1.1)
hashie (>= 3.0)
health_check (2.6.0)
Loading
Loading
@@ -415,7 +415,7 @@ GEM
json (~> 1.8)
multi_xml (>= 0.5.2)
httpclient (2.8.2)
i18n (0.9.1)
i18n (0.9.5)
concurrent-ruby (~> 1.0)
ice_nine (0.11.2)
influxdb (0.2.3)
Loading
Loading
@@ -427,10 +427,6 @@ GEM
multipart-post
oauth (~> 0.5, >= 0.5.0)
jquery-atwho-rails (1.3.2)
jquery-rails (4.3.1)
rails-dom-testing (>= 1, < 3)
railties (>= 4.2.0)
thor (>= 0.14, < 2.0)
json (1.8.6)
json-jwt (1.7.2)
activesupport
Loading
Loading
@@ -454,7 +450,7 @@ GEM
kaminari-core (= 1.0.1)
kaminari-core (1.0.1)
kgio (2.10.0)
knapsack (1.11.0)
knapsack (1.16.0)
rake
timecop (>= 0.1.0)
kubeclient (3.0.0)
Loading
Loading
@@ -513,7 +509,7 @@ GEM
mustermann (~> 1.0.0)
mysql2 (0.4.10)
net-ldap (0.16.0)
net-ssh (4.1.0)
net-ssh (4.2.0)
netrc (0.11.0)
nokogiri (1.8.2)
mini_portile2 (~> 2.3.0)
Loading
Loading
@@ -525,12 +521,12 @@ GEM
multi_json (~> 1.3)
multi_xml (~> 0.5)
rack (>= 1.2, < 3)
octokit (4.6.2)
octokit (4.8.0)
sawyer (~> 0.8.0, >= 0.5.3)
oj (2.17.5)
omniauth (1.4.2)
omniauth (1.4.3)
hashie (>= 1.2, < 4)
rack (>= 1.0, < 3)
rack (>= 1.6.2, < 3)
omniauth-auth0 (1.4.1)
omniauth-oauth2 (~> 1.1)
omniauth-authentiq (0.3.1)
Loading
Loading
@@ -569,9 +565,9 @@ GEM
omniauth (~> 1.2)
omniauth-oauth2-generic (0.2.2)
omniauth-oauth2 (~> 1.0)
omniauth-saml (1.7.0)
omniauth (~> 1.3)
ruby-saml (~> 1.4)
omniauth-saml (1.10.0)
omniauth (~> 1.3, >= 1.3.2)
ruby-saml (~> 1.7)
omniauth-shibboleth (1.2.1)
omniauth (>= 1.0.0)
omniauth-twitter (1.2.1)
Loading
Loading
@@ -650,7 +646,7 @@ GEM
pry (>= 0.9.10)
public_suffix (3.0.2)
pyu-ruby-sasl (0.0.3.3)
rack (1.6.8)
rack (1.6.9)
rack-accept (0.4.5)
rack (>= 0.4)
rack-attack (4.4.1)
Loading
Loading
@@ -803,9 +799,9 @@ GEM
i18n
ruby-fogbugz (0.2.1)
crack (~> 0.4)
ruby-prof (0.16.2)
ruby-prof (0.17.0)
ruby-progressbar (1.9.0)
ruby-saml (1.4.1)
ruby-saml (1.7.2)
nokogiri (>= 1.5.10)
ruby_parser (3.9.0)
sexp_processor (~> 4.1)
Loading
Loading
@@ -844,7 +840,7 @@ GEM
selenium-webdriver (3.5.0)
childprocess (~> 0.5)
rubyzip (~> 1.0)
sentry-raven (2.5.3)
sentry-raven (2.7.2)
faraday (>= 0.7.6, < 1.0)
settingslogic (2.0.9)
sexp_processor (4.9.0)
Loading
Loading
@@ -933,7 +929,7 @@ GEM
truncato (0.7.10)
htmlentities (~> 4.3.1)
nokogiri (~> 1.8.0, >= 1.7.0)
tzinfo (1.2.4)
tzinfo (1.2.5)
thread_safe (~> 0.1)
u2f (0.2.1)
uber (0.1.0)
Loading
Loading
@@ -994,7 +990,7 @@ DEPENDENCIES
RedCloth (~> 4.3.2)
ace-rails-ap (~> 4.1.0)
activerecord_sane_schema_dumper (= 0.2)
acts-as-taggable-on (~> 4.0)
acts-as-taggable-on (~> 5.0)
addressable (~> 2.5.2)
akismet (~> 2.0)
allocations (~> 1.0)
Loading
Loading
@@ -1044,18 +1040,18 @@ DEPENDENCIES
fast_blank
ffaker (~> 2.4)
flay (~> 2.10.0)
flipper (~> 0.11.0)
flipper-active_record (~> 0.11.0)
flipper-active_support_cache_store (~> 0.11.0)
flipper (~> 0.13.0)
flipper-active_record (~> 0.13.0)
flipper-active_support_cache_store (~> 0.13.0)
fog-aliyun (~> 0.2.0)
fog-aws (~> 1.4)
fog-aws (~> 2.0)
fog-core (~> 1.44)
fog-google (~> 0.5)
fog-local (~> 0.3)
fog-openstack (~> 0.1)
fog-rackspace (~> 0.1.1)
font-awesome-rails (~> 4.7)
foreman (~> 0.78.0)
foreman (~> 0.84.0)
fuubar (~> 2.2.0)
gemnasium-gitlab-service (~> 0.2)
gemojione (~> 3.3)
Loading
Loading
@@ -1090,11 +1086,10 @@ DEPENDENCIES
influxdb (~> 0.2)
jira-ruby (~> 1.4)
jquery-atwho-rails (~> 1.3.2)
jquery-rails (~> 4.3.1)
json-schema (~> 2.8.0)
jwt (~> 1.5.6)
kaminari (~> 1.0)
knapsack (~> 1.11.0)
knapsack (~> 1.16)
kubeclient (~> 3.0)
letter_opener_web (~> 1.3.0)
license_finder (~> 3.1)
Loading
Loading
@@ -1107,10 +1102,10 @@ DEPENDENCIES
mousetrap-rails (~> 1.4.6)
mysql2 (~> 0.4.10)
net-ldap
net-ssh (~> 4.1.0)
net-ssh (~> 4.2.0)
nokogiri (~> 1.8.2)
oauth2 (~> 1.4)
octokit (~> 4.6.2)
octokit (~> 4.8)
oj (~> 2.17.4)
omniauth (~> 1.4.2)
omniauth-auth0 (~> 1.4.1)
Loading
Loading
@@ -1123,7 +1118,7 @@ DEPENDENCIES
omniauth-google-oauth2 (~> 0.5.2)
omniauth-kerberos (~> 0.3.0)
omniauth-oauth2-generic (~> 0.2.2)
omniauth-saml (~> 1.7.0)
omniauth-saml (~> 1.10.0)
omniauth-shibboleth (~> 1.2.0)
omniauth-twitter (~> 1.2.0)
omniauth_crowd (~> 2.2.0)
Loading
Loading
@@ -1173,7 +1168,7 @@ DEPENDENCIES
rubocop (~> 0.52.1)
rubocop-rspec (~> 1.22.1)
ruby-fogbugz (~> 0.2.1)
ruby-prof (~> 0.16.2)
ruby-prof (~> 0.17.0)
ruby_parser (~> 3.8)
rufus-scheduler (~> 3.4)
rugged (~> 0.26.0)
Loading
Loading
@@ -1183,7 +1178,7 @@ DEPENDENCIES
seed-fu (~> 2.3.7)
select2-rails (~> 3.5.9)
selenium-webdriver (~> 3.5)
sentry-raven (~> 2.5.3)
sentry-raven (~> 2.7)
settingslogic (~> 2.0.9)
sham_rack (~> 1.3.6)
shoulda-matchers (~> 3.1.2)
Loading
Loading
Loading
Loading
@@ -132,9 +132,8 @@ class GfmAutoComplete {
callbacks: {
...this.getDefaultCallbacks(),
matcher(flag, subtext) {
const relevantText = subtext.trim().split(/\s/).pop();
const regexp = new RegExp(`(?:[^${glRegexp.unicodeLetters}0-9:]|\n|^):([^:]*)$`, 'gi');
const match = regexp.exec(relevantText);
const match = regexp.exec(subtext);
 
return match && match.length ? match[1] : null;
},
Loading
Loading
Loading
Loading
@@ -73,6 +73,7 @@ export default class MergeRequestTabs {
constructor({ action, setUrl, stubLocation } = {}) {
const mergeRequestTabs = document.querySelector('.js-tabs-affix');
const navbar = document.querySelector('.navbar-gitlab');
const peek = document.getElementById('peek');
const paddingTop = 16;
 
this.diffsLoaded = false;
Loading
Loading
@@ -86,6 +87,10 @@ export default class MergeRequestTabs {
this.showTab = this.showTab.bind(this);
this.stickyTop = navbar ? navbar.offsetHeight - paddingTop : 0;
 
if (peek) {
this.stickyTop += peek.offsetHeight;
}
if (mergeRequestTabs) {
this.stickyTop += mergeRequestTabs.offsetHeight;
}
Loading
Loading
Loading
Loading
@@ -209,6 +209,7 @@
 
const xAxis = d3.axisBottom()
.scale(axisXScale)
.ticks(this.graphWidth / 120)
.tickFormat(timeScaleFormat);
 
const yAxis = d3.axisLeft()
Loading
Loading
Loading
Loading
@@ -4,13 +4,15 @@ import discussionCounter from '../notes/components/discussion_counter.vue';
import store from '../notes/stores';
 
export default function initMrNotes() {
new Vue({ // eslint-disable-line
// eslint-disable-next-line no-new
new Vue({
el: '#js-vue-mr-discussions',
components: {
notesApp,
},
data() {
const notesDataset = document.getElementById('js-vue-mr-discussions').dataset;
const notesDataset = document.getElementById('js-vue-mr-discussions')
.dataset;
return {
noteableData: JSON.parse(notesDataset.noteableData),
currentUserData: JSON.parse(notesDataset.currentUserData),
Loading
Loading
@@ -28,7 +30,8 @@ export default function initMrNotes() {
},
});
 
new Vue({ // eslint-disable-line
// eslint-disable-next-line no-new
new Vue({
el: '#js-vue-discussion-counter',
components: {
discussionCounter,
Loading
Loading
Loading
Loading
@@ -16,6 +16,10 @@ import Autosize from 'autosize';
import 'vendor/jquery.caret'; // required by jquery.atwho
import 'vendor/jquery.atwho';
import AjaxCache from '~/lib/utils/ajax_cache';
import Vue from 'vue';
import syntaxHighlight from '~/syntax_highlight';
import SkeletonLoadingContainer from '~/vue_shared/components/skeleton_loading_container.vue';
import { __ } from '~/locale';
import axios from './lib/utils/axios_utils';
import { getLocationHash } from './lib/utils/url_utility';
import Flash from './flash';
Loading
Loading
@@ -24,7 +28,13 @@ import GLForm from './gl_form';
import loadAwardsHandler from './awards_handler';
import Autosave from './autosave';
import TaskList from './task_list';
import { isInViewport, getPagePath, scrollToElement, isMetaKey, hasVueMRDiscussionsCookie } from './lib/utils/common_utils';
import {
isInViewport,
getPagePath,
scrollToElement,
isMetaKey,
hasVueMRDiscussionsCookie,
} from './lib/utils/common_utils';
import imageDiffHelper from './image_diff/helpers/index';
import { localTimeAgo } from './lib/utils/datetime_utility';
 
Loading
Loading
@@ -38,9 +48,21 @@ const MAX_VISIBLE_COMMIT_LIST_COUNT = 3;
const REGEX_QUICK_ACTIONS = /^\/\w+.*$/gm;
 
export default class Notes {
static initialize(notes_url, note_ids, last_fetched_at, view, enableGFM = true) {
static initialize(
notes_url,
note_ids,
last_fetched_at,
view,
enableGFM = true,
) {
if (!this.instance) {
this.instance = new Notes(notes_url, note_ids, last_fetched_at, view, enableGFM);
this.instance = new Notes(
notes_url,
note_ids,
last_fetched_at,
view,
enableGFM,
);
}
}
 
Loading
Loading
@@ -78,7 +100,8 @@ export default class Notes {
this.updatedNotesTrackingMap = {};
this.last_fetched_at = last_fetched_at;
this.noteable_url = document.URL;
this.notesCountBadge || (this.notesCountBadge = $('.issuable-details').find('.notes-tab .badge'));
this.notesCountBadge ||
(this.notesCountBadge = $('.issuable-details').find('.notes-tab .badge'));
this.basePollingInterval = 15000;
this.maxPollingSteps = 4;
 
Loading
Loading
@@ -89,15 +112,24 @@ export default class Notes {
this.taskList = new TaskList({
dataType: 'note',
fieldName: 'note',
selector: '.notes'
selector: '.notes',
});
this.collapseLongCommitList();
this.setViewType(view);
 
// We are in the Merge Requests page so we need another edit form for Changes tab
if (getPagePath(1) === 'merge_requests') {
$('.note-edit-form').clone()
.addClass('mr-note-edit-form').insertAfter('.note-edit-form');
$('.note-edit-form')
.clone()
.addClass('mr-note-edit-form')
.insertAfter('.note-edit-form');
}
const hash = getLocationHash();
const $anchor = hash && document.getElementById(hash);
if ($anchor) {
this.loadLazyDiff({ currentTarget: $anchor });
}
}
 
Loading
Loading
@@ -106,7 +138,9 @@ export default class Notes {
}
 
addBinding() {
this.$wrapperEl = hasVueMRDiscussionsCookie() ? $(document).find('.diffs') : $(document);
this.$wrapperEl = hasVueMRDiscussionsCookie()
? $(document).find('.diffs')
: $(document);
 
// Edit note link
this.$wrapperEl.on('click', '.js-note-edit', this.showEditForm.bind(this));
Loading
Loading
@@ -114,36 +148,78 @@ export default class Notes {
// Reopen and close actions for Issue/MR combined with note form submit
this.$wrapperEl.on('click', '.js-comment-submit-button', this.postComment);
this.$wrapperEl.on('click', '.js-comment-save-button', this.updateComment);
this.$wrapperEl.on('keyup input', '.js-note-text', this.updateTargetButtons);
this.$wrapperEl.on(
'keyup input',
'.js-note-text',
this.updateTargetButtons,
);
// resolve a discussion
this.$wrapperEl.on('click', '.js-comment-resolve-button', this.postComment);
// remove a note (in general)
this.$wrapperEl.on('click', '.js-note-delete', this.removeNote);
// delete note attachment
this.$wrapperEl.on('click', '.js-note-attachment-delete', this.removeAttachment);
this.$wrapperEl.on(
'click',
'.js-note-attachment-delete',
this.removeAttachment,
);
// reset main target form when clicking discard
this.$wrapperEl.on('click', '.js-note-discard', this.resetMainTargetForm);
// update the file name when an attachment is selected
this.$wrapperEl.on('change', '.js-note-attachment-input', this.updateFormAttachment);
this.$wrapperEl.on(
'change',
'.js-note-attachment-input',
this.updateFormAttachment,
);
// reply to diff/discussion notes
this.$wrapperEl.on('click', '.js-discussion-reply-button', this.onReplyToDiscussionNote);
this.$wrapperEl.on(
'click',
'.js-discussion-reply-button',
this.onReplyToDiscussionNote,
);
// add diff note
this.$wrapperEl.on('click', '.js-add-diff-note-button', this.onAddDiffNote);
// add diff note for images
this.$wrapperEl.on('click', '.js-add-image-diff-note-button', this.onAddImageDiffNote);
this.$wrapperEl.on(
'click',
'.js-add-image-diff-note-button',
this.onAddImageDiffNote,
);
// hide diff note form
this.$wrapperEl.on('click', '.js-close-discussion-note-form', this.cancelDiscussionForm);
this.$wrapperEl.on(
'click',
'.js-close-discussion-note-form',
this.cancelDiscussionForm,
);
// toggle commit list
this.$wrapperEl.on('click', '.system-note-commit-list-toggler', this.toggleCommitList);
this.$wrapperEl.on(
'click',
'.system-note-commit-list-toggler',
this.toggleCommitList,
);
this.$wrapperEl.on('click', '.js-toggle-lazy-diff', this.loadLazyDiff);
// fetch notes when tab becomes visible
this.$wrapperEl.on('visibilitychange', this.visibilityChange);
// when issue status changes, we need to refresh data
this.$wrapperEl.on('issuable:change', this.refresh);
// ajax:events that happen on Form when actions like Reopen, Close are performed on Issues and MRs.
this.$wrapperEl.on('ajax:success', '.js-main-target-form', this.addNote);
this.$wrapperEl.on('ajax:success', '.js-discussion-note-form', this.addDiscussionNote);
this.$wrapperEl.on('ajax:success', '.js-main-target-form', this.resetMainTargetForm);
this.$wrapperEl.on('ajax:complete', '.js-main-target-form', this.reenableTargetFormSubmitButton);
this.$wrapperEl.on(
'ajax:success',
'.js-discussion-note-form',
this.addDiscussionNote,
);
this.$wrapperEl.on(
'ajax:success',
'.js-main-target-form',
this.resetMainTargetForm,
);
this.$wrapperEl.on(
'ajax:complete',
'.js-main-target-form',
this.reenableTargetFormSubmitButton,
);
// when a key is clicked on the notes
this.$wrapperEl.on('keydown', '.js-note-text', this.keydownNoteText);
// When the URL fragment/hash has changed, `#note_xxx`
Loading
Loading
@@ -173,6 +249,7 @@ export default class Notes {
this.$wrapperEl.off('keydown', '.js-note-text');
this.$wrapperEl.off('click', '.js-comment-resolve-button');
this.$wrapperEl.off('click', '.system-note-commit-list-toggler');
this.$wrapperEl.off('click', '.js-toggle-lazy-diff');
this.$wrapperEl.off('ajax:success', '.js-main-target-form');
this.$wrapperEl.off('ajax:success', '.js-discussion-note-form');
this.$wrapperEl.off('ajax:complete', '.js-main-target-form');
Loading
Loading
@@ -181,10 +258,16 @@ export default class Notes {
}
 
static initCommentTypeToggle(form) {
const dropdownTrigger = form.querySelector('.js-comment-type-dropdown .dropdown-toggle');
const dropdownList = form.querySelector('.js-comment-type-dropdown .dropdown-menu');
const dropdownTrigger = form.querySelector(
'.js-comment-type-dropdown .dropdown-toggle',
);
const dropdownList = form.querySelector(
'.js-comment-type-dropdown .dropdown-menu',
);
const noteTypeInput = form.querySelector('#note_type');
const submitButton = form.querySelector('.js-comment-type-dropdown .js-comment-submit-button');
const submitButton = form.querySelector(
'.js-comment-type-dropdown .js-comment-submit-button',
);
const closeButton = form.querySelector('.js-note-target-close');
const reopenButton = form.querySelector('.js-note-target-reopen');
 
Loading
Loading
@@ -201,7 +284,13 @@ export default class Notes {
}
 
keydownNoteText(e) {
var $textarea, discussionNoteForm, editNote, myLastNote, myLastNoteEditBtn, newText, originalText;
var $textarea,
discussionNoteForm,
editNote,
myLastNote,
myLastNoteEditBtn,
newText,
originalText;
if (isMetaKey(e)) {
return;
}
Loading
Loading
@@ -213,7 +302,12 @@ export default class Notes {
if ($textarea.val() !== '') {
return;
}
myLastNote = $(`li.note[data-author-id='${gon.current_user_id}'][data-editable]:last`, $textarea.closest('.note, .notes_holder, #notes'));
myLastNote = $(
`li.note[data-author-id='${
gon.current_user_id
}'][data-editable]:last`,
$textarea.closest('.note, .notes_holder, #notes'),
);
if (myLastNote.length) {
myLastNoteEditBtn = myLastNote.find('.js-note-edit');
return myLastNoteEditBtn.trigger('click', [true, myLastNote]);
Loading
Loading
@@ -224,7 +318,9 @@ export default class Notes {
discussionNoteForm = $textarea.closest('.js-discussion-note-form');
if (discussionNoteForm.length) {
if ($textarea.val() !== '') {
if (!confirm('Are you sure you want to cancel creating this comment?')) {
if (
!confirm('Are you sure you want to cancel creating this comment?')
) {
return;
}
}
Loading
Loading
@@ -236,7 +332,9 @@ export default class Notes {
originalText = $textarea.closest('form').data('originalNote');
newText = $textarea.val();
if (originalText !== newText) {
if (!confirm('Are you sure you want to cancel editing this comment?')) {
if (
!confirm('Are you sure you want to cancel editing this comment?')
) {
return;
}
}
Loading
Loading
@@ -249,11 +347,14 @@ export default class Notes {
if (Notes.interval) {
clearInterval(Notes.interval);
}
return Notes.interval = setInterval((function(_this) {
return function() {
return _this.refresh();
};
})(this), this.pollingInterval);
return (Notes.interval = setInterval(
(function(_this) {
return function() {
return _this.refresh();
};
})(this),
this.pollingInterval,
));
}
 
refresh() {
Loading
Loading
@@ -269,20 +370,23 @@ export default class Notes {
 
this.refreshing = true;
 
axios.get(`${this.notes_url}?html=true`, {
headers: {
'X-Last-Fetched-At': this.last_fetched_at,
},
}).then(({ data }) => {
const notes = data.notes;
this.last_fetched_at = data.last_fetched_at;
this.setPollingInterval(data.notes.length);
$.each(notes, (i, note) => this.renderNote(note));
this.refreshing = false;
}).catch(() => {
this.refreshing = false;
});
axios
.get(`${this.notes_url}?html=true`, {
headers: {
'X-Last-Fetched-At': this.last_fetched_at,
},
})
.then(({ data }) => {
const notes = data.notes;
this.last_fetched_at = data.last_fetched_at;
this.setPollingInterval(data.notes.length);
$.each(notes, (i, note) => this.renderNote(note));
this.refreshing = false;
})
.catch(() => {
this.refreshing = false;
});
}
 
/**
Loading
Loading
@@ -298,7 +402,8 @@ export default class Notes {
if (shouldReset == null) {
shouldReset = true;
}
nthInterval = this.basePollingInterval * Math.pow(2, this.maxPollingSteps - 1);
nthInterval =
this.basePollingInterval * Math.pow(2, this.maxPollingSteps - 1);
if (shouldReset) {
this.pollingInterval = this.basePollingInterval;
} else if (this.pollingInterval < nthInterval) {
Loading
Loading
@@ -317,12 +422,17 @@ export default class Notes {
if ('emoji_award' in noteEntity.commands_changes) {
votesBlock = $('.js-awards-block').eq(0);
 
loadAwardsHandler().then((awardsHandler) => {
awardsHandler.addAwardToEmojiBar(votesBlock, noteEntity.commands_changes.emoji_award);
awardsHandler.scrollToAwards();
}).catch(() => {
// ignore
});
loadAwardsHandler()
.then(awardsHandler => {
awardsHandler.addAwardToEmojiBar(
votesBlock,
noteEntity.commands_changes.emoji_award,
);
awardsHandler.scrollToAwards();
})
.catch(() => {
// ignore
});
}
}
}
Loading
Loading
@@ -367,11 +477,17 @@ export default class Notes {
 
if (!noteEntity.valid) {
if (noteEntity.errors && noteEntity.errors.commands_only) {
if (noteEntity.commands_changes &&
Object.keys(noteEntity.commands_changes).length > 0) {
if (
noteEntity.commands_changes &&
Object.keys(noteEntity.commands_changes).length > 0
) {
$notesList.find('.system-note.being-posted').remove();
}
this.addFlash(noteEntity.errors.commands_only, 'notice', this.parentTimeline.get(0));
this.addFlash(
noteEntity.errors.commands_only,
'notice',
this.parentTimeline.get(0),
);
this.refresh();
}
return;
Loading
Loading
@@ -393,28 +509,30 @@ export default class Notes {
this.setupNewNote($newNote);
this.refresh();
return this.updateNotesCount(1);
}
// The server can send the same update multiple times so we need to make sure to only update once per actual update.
else if (Notes.isUpdatedNote(noteEntity, $note)) {
} else if (Notes.isUpdatedNote(noteEntity, $note)) {
// The server can send the same update multiple times so we need to make sure to only update once per actual update.
const isEditing = $note.hasClass('is-editing');
const initialContent = normalizeNewlines(
$note.find('.original-note-content').text().trim()
$note
.find('.original-note-content')
.text()
.trim(),
);
const $textarea = $note.find('.js-note-text');
const currentContent = $textarea.val();
// There can be CRLF vs LF mismatches if we don't sanitize and compare the same way
const sanitizedNoteNote = normalizeNewlines(noteEntity.note);
const isTextareaUntouched = currentContent === initialContent || currentContent === sanitizedNoteNote;
const isTextareaUntouched =
currentContent === initialContent ||
currentContent === sanitizedNoteNote;
 
if (isEditing && isTextareaUntouched) {
$textarea.val(noteEntity.note);
this.updatedNotesTrackingMap[noteEntity.id] = noteEntity;
}
else if (isEditing && !isTextareaUntouched) {
} else if (isEditing && !isTextareaUntouched) {
this.putConflictEditWarningInPlace(noteEntity, $note);
this.updatedNotesTrackingMap[noteEntity.id] = noteEntity;
}
else {
} else {
const $updatedNote = Notes.animateUpdateNote(noteEntity.html, $note);
this.setupNewNote($updatedNote);
}
Loading
Loading
@@ -438,17 +556,31 @@ export default class Notes {
}
this.note_ids.push(noteEntity.id);
 
form = $form || $(`.js-discussion-note-form[data-discussion-id="${noteEntity.discussion_id}"]`);
row = (form.length || !noteEntity.discussion_line_code) ? form.closest('tr') : $(`#${noteEntity.discussion_line_code}`);
form =
$form ||
$(
`.js-discussion-note-form[data-discussion-id="${
noteEntity.discussion_id
}"]`,
);
row =
form.length || !noteEntity.discussion_line_code
? form.closest('tr')
: $(`#${noteEntity.discussion_line_code}`);
 
if (noteEntity.on_image) {
row = form;
}
 
lineType = this.isParallelView() ? form.find('#line_type').val() : 'old';
diffAvatarContainer = row.prevAll('.line_holder').first().find('.js-avatar-container.' + lineType + '_line');
diffAvatarContainer = row
.prevAll('.line_holder')
.first()
.find('.js-avatar-container.' + lineType + '_line');
// is this the first note of discussion?
discussionContainer = $(`.notes[data-discussion-id="${noteEntity.discussion_id}"]`);
discussionContainer = $(
`.notes[data-discussion-id="${noteEntity.discussion_id}"]`,
);
if (!discussionContainer.length) {
discussionContainer = form.closest('.discussion').find('.notes');
}
Loading
Loading
@@ -456,25 +588,42 @@ export default class Notes {
if (noteEntity.diff_discussion_html) {
var $discussion = $(noteEntity.diff_discussion_html).renderGFM();
 
if (!this.isParallelView() || row.hasClass('js-temp-notes-holder') || noteEntity.on_image) {
if (
!this.isParallelView() ||
row.hasClass('js-temp-notes-holder') ||
noteEntity.on_image
) {
// insert the note and the reply button after the temp row
row.after($discussion);
} else {
// Merge new discussion HTML in
var $notes = $discussion.find(`.notes[data-discussion-id="${noteEntity.discussion_id}"]`);
var contentContainerClass = '.' + $notes.closest('.notes_content')
.attr('class')
.split(' ')
.join('.');
row.find(contentContainerClass + ' .content').append($notes.closest('.content').children());
var $notes = $discussion.find(
`.notes[data-discussion-id="${noteEntity.discussion_id}"]`,
);
var contentContainerClass =
'.' +
$notes
.closest('.notes_content')
.attr('class')
.split(' ')
.join('.');
row
.find(contentContainerClass + ' .content')
.append($notes.closest('.content').children());
}
}
// Init discussion on 'Discussion' page if it is merge request page
const page = $('body').attr('data-page');
if ((page && page.indexOf('projects:merge_request') !== -1) || !noteEntity.diff_discussion_html) {
if (
(page && page.indexOf('projects:merge_request') !== -1) ||
!noteEntity.diff_discussion_html
) {
if (!hasVueMRDiscussionsCookie()) {
Notes.animateAppendNote(noteEntity.discussion_html, $('.main-notes-list'));
Notes.animateAppendNote(
noteEntity.discussion_html,
$('.main-notes-list'),
);
}
}
} else {
Loading
Loading
@@ -482,7 +631,10 @@ export default class Notes {
Notes.animateAppendNote(noteEntity.html, discussionContainer);
}
 
if (typeof gl.diffNotesCompileComponents !== 'undefined' && noteEntity.discussion_resolvable) {
if (
typeof gl.diffNotesCompileComponents !== 'undefined' &&
noteEntity.discussion_resolvable
) {
gl.diffNotesCompileComponents();
 
this.renderDiscussionAvatar(diffAvatarContainer, noteEntity);
Loading
Loading
@@ -494,7 +646,8 @@ export default class Notes {
}
 
getLineHolder(changesDiscussionContainer) {
return $(changesDiscussionContainer).closest('.notes_holder')
return $(changesDiscussionContainer)
.closest('.notes_holder')
.prevAll('.line_holder')
.first()
.get(0);
Loading
Loading
@@ -527,8 +680,14 @@ export default class Notes {
form.find('.js-errors').remove();
// reset text and preview
form.find('.js-md-write-button').click();
form.find('.js-note-text').val('').trigger('input');
form.find('.js-note-text').data('autosave').reset();
form
.find('.js-note-text')
.val('')
.trigger('input');
form
.find('.js-note-text')
.data('autosave')
.reset();
 
var event = document.createEvent('Event');
event.initEvent('autosize:update', true, false);
Loading
Loading
@@ -564,7 +723,10 @@ export default class Notes {
form.find('#note_type').val('');
form.find('#note_project_id').remove();
form.find('#in_reply_to_discussion_id').remove();
form.find('.js-comment-resolve-button').closest('comment-and-resolve-btn').remove();
form
.find('.js-comment-resolve-button')
.closest('comment-and-resolve-btn')
.remove();
this.parentTimeline = form.parents('.timeline');
 
if (form.length) {
Loading
Loading
@@ -618,11 +780,17 @@ export default class Notes {
} else if ($form.hasClass('js-discussion-note-form')) {
formParentTimeline = $form.closest('.discussion-notes').find('.notes');
}
return this.addFlash('Your comment could not be submitted! Please check your network connection and try again.', 'alert', formParentTimeline.get(0));
return this.addFlash(
'Your comment could not be submitted! Please check your network connection and try again.',
'alert',
formParentTimeline.get(0),
);
}
 
updateNoteError($parentTimeline) {
new Flash('Your comment could not be updated! Please check your network connection and try again.');
new Flash(
'Your comment could not be updated! Please check your network connection and try again.',
);
}
 
/**
Loading
Loading
@@ -671,14 +839,16 @@ export default class Notes {
}
 
checkContentToAllowEditing($el) {
var initialContent = $el.find('.original-note-content').text().trim();
var initialContent = $el
.find('.original-note-content')
.text()
.trim();
var currentContent = $el.find('.js-note-text').val();
var isAllowed = true;
 
if (currentContent === initialContent) {
this.removeNoteEditForm($el);
}
else {
} else {
var $buttons = $el.find('.note-form-actions');
var isWidgetVisible = isInViewport($el.get(0));
 
Loading
Loading
@@ -740,8 +910,7 @@ export default class Notes {
this.setupNewNote($newNote);
// Now that we have taken care of the update, clear it out
delete this.updatedNotesTrackingMap[noteId];
}
else {
} else {
$note.find('.js-finish-edit-warning').hide();
this.removeNoteEditForm($note);
}
Loading
Loading
@@ -774,7 +943,9 @@ export default class Notes {
form.removeClass('current-note-edit-form');
form.find('.js-finish-edit-warning').hide();
// Replace markdown textarea text with original note text.
return form.find('.js-note-text').val(form.find('form.edit-note').data('originalNote'));
return form
.find('.js-note-text')
.val(form.find('form.edit-note').data('originalNote'));
}
 
/**
Loading
Loading
@@ -788,58 +959,67 @@ export default class Notes {
$note = $(e.currentTarget).closest('.note');
noteElId = $note.attr('id');
noteId = $note.attr('data-note-id');
lineHolder = $(e.currentTarget).closest('.notes[data-discussion-id]')
lineHolder = $(e.currentTarget)
.closest('.notes[data-discussion-id]')
.closest('.notes_holder')
.prev('.line_holder');
$(`.note[id="${noteElId}"]`).each((function(_this) {
// A same note appears in the "Discussion" and in the "Changes" tab, we have
// to remove all. Using $('.note[id='noteId']') ensure we get all the notes,
// where $('#noteId') would return only one.
return function(i, el) {
var $note, $notes;
$note = $(el);
$notes = $note.closest('.discussion-notes');
const discussionId = $('.notes', $notes).data('discussionId');
if (typeof gl.diffNotesCompileComponents !== 'undefined') {
if (gl.diffNoteApps[noteElId]) {
gl.diffNoteApps[noteElId].$destroy();
$(`.note[id="${noteElId}"]`).each(
(function(_this) {
// A same note appears in the "Discussion" and in the "Changes" tab, we have
// to remove all. Using $('.note[id='noteId']') ensure we get all the notes,
// where $('#noteId') would return only one.
return function(i, el) {
var $note, $notes;
$note = $(el);
$notes = $note.closest('.discussion-notes');
const discussionId = $('.notes', $notes).data('discussionId');
if (typeof gl.diffNotesCompileComponents !== 'undefined') {
if (gl.diffNoteApps[noteElId]) {
gl.diffNoteApps[noteElId].$destroy();
}
}
}
$note.remove();
// check if this is the last note for this line
if ($notes.find('.note').length === 0) {
var notesTr = $notes.closest('tr');
// "Discussions" tab
$notes.closest('.timeline-entry').remove();
$(`.js-diff-avatars-${discussionId}`).trigger('remove.vue');
// The notes tr can contain multiple lists of notes, like on the parallel diff
// notesTr does not exist for image diffs
if (notesTr.find('.discussion-notes').length > 1 || notesTr.length === 0) {
const $diffFile = $notes.closest('.diff-file');
if ($diffFile.length > 0) {
const removeBadgeEvent = new CustomEvent('removeBadge.imageDiff', {
detail: {
// badgeNumber's start with 1 and index starts with 0
badgeNumber: $notes.index() + 1,
},
});
 
$diffFile[0].dispatchEvent(removeBadgeEvent);
$note.remove();
// check if this is the last note for this line
if ($notes.find('.note').length === 0) {
var notesTr = $notes.closest('tr');
// "Discussions" tab
$notes.closest('.timeline-entry').remove();
$(`.js-diff-avatars-${discussionId}`).trigger('remove.vue');
// The notes tr can contain multiple lists of notes, like on the parallel diff
// notesTr does not exist for image diffs
if (
notesTr.find('.discussion-notes').length > 1 ||
notesTr.length === 0
) {
const $diffFile = $notes.closest('.diff-file');
if ($diffFile.length > 0) {
const removeBadgeEvent = new CustomEvent(
'removeBadge.imageDiff',
{
detail: {
// badgeNumber's start with 1 and index starts with 0
badgeNumber: $notes.index() + 1,
},
},
);
$diffFile[0].dispatchEvent(removeBadgeEvent);
}
$notes.remove();
} else if (notesTr.length > 0) {
notesTr.remove();
}
$notes.remove();
} else if (notesTr.length > 0) {
notesTr.remove();
}
}
};
})(this));
};
})(this),
);
 
Notes.refreshVueNotes();
Notes.checkMergeRequestStatus();
Loading
Loading
@@ -921,7 +1101,12 @@ export default class Notes {
// DiffNote
form.find('#note_position').val(dataHolder.attr('data-position'));
 
form.find('.js-note-discard').show().removeClass('js-note-discard').addClass('js-close-discussion-note-form').text(form.find('.js-close-discussion-note-form').data('cancelText'));
form
.find('.js-note-discard')
.show()
.removeClass('js-note-discard')
.addClass('js-close-discussion-note-form')
.text(form.find('.js-close-discussion-note-form').data('cancelText'));
form.find('.js-note-target-close').remove();
form.find('.js-note-new-discussion').remove();
this.setupNoteForm(form);
Loading
Loading
@@ -957,7 +1142,7 @@ export default class Notes {
this.toggleDiffNote({
target: $link,
lineType: link.dataset.lineType,
showReplyInput
showReplyInput,
});
}
 
Loading
Loading
@@ -973,7 +1158,9 @@ export default class Notes {
 
// Setup comment form
let newForm;
const $noteContainer = $link.closest('.diff-viewer').find('.note-container');
const $noteContainer = $link
.closest('.diff-viewer')
.find('.note-container');
const $form = $noteContainer.find('> .discussion-form');
 
if ($form.length === 0) {
Loading
Loading
@@ -986,13 +1173,17 @@ export default class Notes {
this.setupDiscussionNoteForm($link, newForm);
}
 
toggleDiffNote({
target,
lineType,
forceShow,
showReplyInput = false,
}) {
var $link, addForm, hasNotes, newForm, noteForm, replyButton, row, rowCssToAdd, targetContent, isDiffCommentAvatar;
toggleDiffNote({ target, lineType, forceShow, showReplyInput = false }) {
var $link,
addForm,
hasNotes,
newForm,
noteForm,
replyButton,
row,
rowCssToAdd,
targetContent,
isDiffCommentAvatar;
$link = $(target);
row = $link.closest('tr');
const nextRow = row.next();
Loading
Loading
@@ -1004,11 +1195,13 @@ export default class Notes {
hasNotes = nextRow.is('.notes_holder');
addForm = false;
let lineTypeSelector = '';
rowCssToAdd = '<tr class="notes_holder js-temp-notes-holder"><td class="notes_line" colspan="2"></td><td class="notes_content"><div class="content"></div></td></tr>';
rowCssToAdd =
'<tr class="notes_holder js-temp-notes-holder"><td class="notes_line" colspan="2"></td><td class="notes_content"><div class="content"></div></td></tr>';
// In parallel view, look inside the correct left/right pane
if (this.isParallelView()) {
lineTypeSelector = `.${lineType}`;
rowCssToAdd = '<tr class="notes_holder js-temp-notes-holder"><td class="notes_line old"></td><td class="notes_content parallel old"><div class="content"></div></td><td class="notes_line new"></td><td class="notes_content parallel new"><div class="content"></div></td></tr>';
rowCssToAdd =
'<tr class="notes_holder js-temp-notes-holder"><td class="notes_line old"></td><td class="notes_content parallel old"><div class="content"></div></td><td class="notes_line new"></td><td class="notes_content parallel new"><div class="content"></div></td></tr>';
}
const notesContentSelector = `.notes_content${lineTypeSelector} .content`;
let notesContent = targetRow.find(notesContentSelector);
Loading
Loading
@@ -1036,7 +1229,9 @@ export default class Notes {
notesContent = targetRow.find(notesContentSelector);
addForm = true;
} else {
const isCurrentlyShown = targetRow.find('.content:not(:empty)').is(':visible');
const isCurrentlyShown = targetRow
.find('.content:not(:empty)')
.is(':visible');
const isForced = forceShow === true || forceShow === false;
const showNow = forceShow === true || (!isCurrentlyShown && !isForced);
 
Loading
Loading
@@ -1063,11 +1258,12 @@ export default class Notes {
row = form.closest('tr');
glForm = form.data('glForm');
glForm.destroy();
form.find('.js-note-text').data('autosave').reset();
// show the reply button (will only work for replies)
form
.prev('.discussion-reply-holder')
.show();
.find('.js-note-text')
.data('autosave')
.reset();
// show the reply button (will only work for replies)
form.prev('.discussion-reply-holder').show();
if (row.is('.js-temp-notes-holder')) {
// remove temporary row for diff lines
return row.remove();
Loading
Loading
@@ -1108,7 +1304,9 @@ export default class Notes {
var filename, form;
form = $(this).closest('form');
// get only the basename
filename = $(this).val().replace(/^.*[\\\/]/, '');
filename = $(this)
.val()
.replace(/^.*[\\\/]/, '');
return form.find('.js-attachment-filename').text(filename);
}
 
Loading
Loading
@@ -1180,12 +1378,16 @@ export default class Notes {
 
this.glForm = new GLForm($editForm.find('form'), this.enableGFM);
 
$editForm.find('form')
$editForm
.find('form')
.attr('action', `${postUrl}?html=true`)
.attr('data-remote', 'true');
$editForm.find('.js-form-target-id').val(targetId);
$editForm.find('.js-form-target-type').val(targetType);
$editForm.find('.js-note-text').focus().val(originalContent);
$editForm
.find('.js-note-text')
.focus()
.val(originalContent);
$editForm.find('.js-md-write-button').trigger('click');
$editForm.find('.referenced-users').hide();
}
Loading
Loading
@@ -1194,7 +1396,9 @@ export default class Notes {
if ($note.find('.js-conflict-edit-warning').length === 0) {
const $alert = $(`<div class="js-conflict-edit-warning alert alert-danger">
This comment has changed since you started editing, please review the
<a href="#note_${noteEntity.id}" target="_blank" rel="noopener noreferrer">
<a href="#note_${
noteEntity.id
}" target="_blank" rel="noopener noreferrer">
updated comment
</a>
to ensure information is not lost
Loading
Loading
@@ -1204,14 +1408,79 @@ export default class Notes {
}
 
updateNotesCount(updateCount) {
return this.notesCountBadge.text(parseInt(this.notesCountBadge.text(), 10) + updateCount);
return this.notesCountBadge.text(
parseInt(this.notesCountBadge.text(), 10) + updateCount,
);
}
static renderPlaceholderComponent($container) {
const el = $container.find('.js-code-placeholder').get(0);
new Vue({
// eslint-disable-line no-new
el,
components: {
SkeletonLoadingContainer,
},
render(createElement) {
return createElement('skeleton-loading-container');
},
});
}
static renderDiffContent($container, data) {
const { discussion_html } = data;
const lines = $(discussion_html).find('.line_holder');
lines.addClass('fade-in');
$container.find('tbody').prepend(lines);
const fileHolder = $container.find('.file-holder');
$container.find('.line-holder-placeholder').remove();
syntaxHighlight(fileHolder);
}
static renderDiffError($container) {
$container.find('.line_content').html(
$(`
<div class="nothing-here-block">
${__(
'Unable to load the diff.',
)} <a class="js-toggle-lazy-diff" href="javascript:void(0)">Try again</a>?
</div>
`),
);
}
loadLazyDiff(e) {
const $container = $(e.currentTarget).closest('.js-toggle-container');
Notes.renderPlaceholderComponent($container);
$container.find('.js-toggle-lazy-diff').removeClass('js-toggle-lazy-diff');
const tableEl = $container.find('tbody');
if (tableEl.length === 0) return;
const fileHolder = $container.find('.file-holder');
const url = fileHolder.data('linesPath');
axios
.get(url)
.then(({ data }) => {
Notes.renderDiffContent($container, data);
})
.catch(() => {
Notes.renderDiffError($container);
});
}
 
toggleCommitList(e) {
const $element = $(e.currentTarget);
const $closestSystemCommitList = $element.siblings('.system-note-commit-list');
const $closestSystemCommitList = $element.siblings(
'.system-note-commit-list',
);
 
$element.find('.fa').toggleClass('fa-angle-down').toggleClass('fa-angle-up');
$element
.find('.fa')
.toggleClass('fa-angle-down')
.toggleClass('fa-angle-up');
$closestSystemCommitList.toggleClass('hide-shade');
}
 
Loading
Loading
@@ -1221,11 +1490,17 @@ export default class Notes {
* intrusive.
*/
collapseLongCommitList() {
const systemNotes = $('#notes-list').find('li.system-note').has('ul');
const systemNotes = $('#notes-list')
.find('li.system-note')
.has('ul');
 
$.each(systemNotes, function(index, systemNote) {
const $systemNote = $(systemNote);
const headerMessage = $systemNote.find('.note-text').find('p:first').text().replace(':', '');
const headerMessage = $systemNote
.find('.note-text')
.find('p:first')
.text()
.replace(':', '');
 
$systemNote.find('.note-header .system-note-message').html(headerMessage);
 
Loading
Loading
@@ -1233,7 +1508,9 @@ export default class Notes {
$systemNote.find('.note-text').addClass('system-note-commit-list');
$systemNote.find('.system-note-commit-list-toggler').show();
} else {
$systemNote.find('.note-text').addClass('system-note-commit-list hide-shade');
$systemNote
.find('.note-text')
.addClass('system-note-commit-list hide-shade');
}
});
}
Loading
Loading
@@ -1251,14 +1528,10 @@ export default class Notes {
 
cleanForm($form) {
// Remove JS classes that are not needed here
$form
.find('.js-comment-type-dropdown')
.removeClass('btn-group');
$form.find('.js-comment-type-dropdown').removeClass('btn-group');
 
// Remove dropdown
$form
.find('.dropdown-menu')
.remove();
$form.find('.dropdown-menu').remove();
 
return $form;
}
Loading
Loading
@@ -1277,7 +1550,11 @@ export default class Notes {
// There can be CRLF vs LF mismatches if we don't sanitize and compare the same way
const sanitizedNoteEntityText = normalizeNewlines(noteEntity.note.trim());
const currentNoteText = normalizeNewlines(
$note.find('.original-note-content').first().text().trim()
$note
.find('.original-note-content')
.first()
.text()
.trim(),
);
return sanitizedNoteEntityText !== currentNoteText;
}
Loading
Loading
@@ -1367,7 +1644,14 @@ export default class Notes {
* Once comment is _actually_ posted on server, we will have final element
* in response that we will show in place of this temporary element.
*/
createPlaceholderNote({ formContent, uniqueId, isDiscussionNote, currentUsername, currentUserFullname, currentUserAvatar }) {
createPlaceholderNote({
formContent,
uniqueId,
isDiscussionNote,
currentUsername,
currentUserFullname,
currentUserAvatar,
}) {
const discussionClass = isDiscussionNote ? 'discussion' : '';
const $tempNote = $(
`<li id="${uniqueId}" class="note being-posted fade-in-half timeline-entry">
Loading
Loading
@@ -1381,8 +1665,12 @@ export default class Notes {
<div class="note-header">
<div class="note-header-info">
<a href="/${_.escape(currentUsername)}">
<span class="hidden-xs">${_.escape(currentUsername)}</span>
<span class="note-headline-light">${_.escape(currentUsername)}</span>
<span class="hidden-xs">${_.escape(
currentUsername,
)}</span>
<span class="note-headline-light">${_.escape(
currentUsername,
)}</span>
</a>
</div>
</div>
Loading
Loading
@@ -1393,11 +1681,13 @@ export default class Notes {
</div>
</div>
</div>
</li>`
</li>`,
);
 
$tempNote.find('.hidden-xs').text(_.escape(currentUserFullname));
$tempNote.find('.note-headline-light').text(`@${_.escape(currentUsername)}`);
$tempNote
.find('.note-headline-light')
.text(`@${_.escape(currentUsername)}`);
 
return $tempNote;
}
Loading
Loading
@@ -1413,7 +1703,7 @@ export default class Notes {
<i>${formContent}</i>
</div>
</div>
</li>`
</li>`,
);
 
return $tempNote;
Loading
Loading
@@ -1445,11 +1735,22 @@ export default class Notes {
const $submitBtn = $(e.target);
let $form = $submitBtn.parents('form');
const $closeBtn = $form.find('.js-note-target-close');
const isDiscussionNote = $submitBtn.parent().find('li.droplab-item-selected').attr('id') === 'discussion';
const isDiscussionNote =
$submitBtn
.parent()
.find('li.droplab-item-selected')
.attr('id') === 'discussion';
const isMainForm = $form.hasClass('js-main-target-form');
const isDiscussionForm = $form.hasClass('js-discussion-note-form');
const isDiscussionResolve = $submitBtn.hasClass('js-comment-resolve-button');
const { formData, formContent, formAction, formContentOriginal } = this.getFormData($form);
const isDiscussionResolve = $submitBtn.hasClass(
'js-comment-resolve-button',
);
const {
formData,
formContent,
formAction,
formContentOriginal,
} = this.getFormData($form);
let noteUniqueId;
let systemNoteUniqueId;
let hasQuickActions = false;
Loading
Loading
@@ -1479,23 +1780,30 @@ export default class Notes {
// Show placeholder note
if (tempFormContent) {
noteUniqueId = _.uniqueId('tempNote_');
$notesContainer.append(this.createPlaceholderNote({
formContent: tempFormContent,
uniqueId: noteUniqueId,
isDiscussionNote,
currentUsername: gon.current_username,
currentUserFullname: gon.current_user_fullname,
currentUserAvatar: gon.current_user_avatar_url,
}));
$notesContainer.append(
this.createPlaceholderNote({
formContent: tempFormContent,
uniqueId: noteUniqueId,
isDiscussionNote,
currentUsername: gon.current_username,
currentUserFullname: gon.current_user_fullname,
currentUserAvatar: gon.current_user_avatar_url,
}),
);
}
 
// Show placeholder system note
if (hasQuickActions) {
systemNoteUniqueId = _.uniqueId('tempSystemNote_');
$notesContainer.append(this.createPlaceholderSystemNote({
formContent: this.getQuickActionDescription(formContent, AjaxCache.get(gl.GfmAutoComplete.dataSources.commands)),
uniqueId: systemNoteUniqueId,
}));
$notesContainer.append(
this.createPlaceholderSystemNote({
formContent: this.getQuickActionDescription(
formContent,
AjaxCache.get(gl.GfmAutoComplete.dataSources.commands),
),
uniqueId: systemNoteUniqueId,
}),
);
}
 
// Clear the form textarea
Loading
Loading
@@ -1509,8 +1817,9 @@ export default class Notes {
 
/* eslint-disable promise/catch-or-return */
// Make request to submit comment on server
axios.post(`${formAction}?html=true`, formData)
.then((res) => {
axios
.post(`${formAction}?html=true`, formData)
.then(res => {
const note = res.data;
 
// Submission successful! remove placeholder
Loading
Loading
@@ -1527,7 +1836,9 @@ export default class Notes {
 
// Reset cached commands list when command is applied
if (hasQuickActions) {
$form.find('textarea.js-note-text').trigger('clear-commands-cache.atwho');
$form
.find('textarea.js-note-text')
.trigger('clear-commands-cache.atwho');
}
 
// Clear previous form errors
Loading
Loading
@@ -1572,11 +1883,14 @@ export default class Notes {
 
// append flash-container to the Notes list
if ($notesContainer.length) {
$notesContainer.append('<div class="flash-container" style="display: none;"></div>');
$notesContainer.append(
'<div class="flash-container" style="display: none;"></div>',
);
}
 
Notes.refreshVueNotes();
} else if (isMainForm) { // Check if this was main thread comment
} else if (isMainForm) {
// Check if this was main thread comment
// Show final note element on UI and perform form and action buttons cleanup
this.addNote($form, note);
this.reenableTargetFormSubmitButton(e);
Loading
Loading
@@ -1587,7 +1901,8 @@ export default class Notes {
}
 
$form.trigger('ajax:success', [note]);
}).catch(() => {
})
.catch(() => {
// Submission failed, remove placeholder note and show Flash error message
$notesContainer.find(`#${noteUniqueId}`).remove();
 
Loading
Loading
@@ -1607,7 +1922,9 @@ export default class Notes {
 
// Show form again on UI on failure
if (isDiscussionForm && $notesContainer.length) {
const replyButton = $notesContainer.parent().find('.js-discussion-reply-button');
const replyButton = $notesContainer
.parent()
.find('.js-discussion-reply-button');
this.replyToDiscussionNote(replyButton[0]);
$form = $notesContainer.parent().find('form');
}
Loading
Loading
@@ -1652,12 +1969,19 @@ export default class Notes {
 
// Show updated comment content temporarily
$noteBodyText.html(formContent);
$editingNote.removeClass('is-editing fade-in-full').addClass('being-posted fade-in-half');
$editingNote.find('.note-headline-meta a').html('<i class="fa fa-spinner fa-spin" aria-label="Comment is being updated" aria-hidden="true"></i>');
$editingNote
.removeClass('is-editing fade-in-full')
.addClass('being-posted fade-in-half');
$editingNote
.find('.note-headline-meta a')
.html(
'<i class="fa fa-spinner fa-spin" aria-label="Comment is being updated" aria-hidden="true"></i>',
);
 
/* eslint-disable promise/catch-or-return */
// Make request to update comment on server
axios.post(`${formAction}?html=true`, formData)
axios
.post(`${formAction}?html=true`, formData)
.then(({ data }) => {
// Submission successful! render final note element
this.updateNote(data, $editingNote);
Loading
Loading
<script>
import $ from 'jquery';
import { mapActions, mapGetters } from 'vuex';
import _ from 'underscore';
import Autosize from 'autosize';
import { __, sprintf } from '~/locale';
import Flash from '../../flash';
import Autosave from '../../autosave';
import TaskList from '../../task_list';
import { capitalizeFirstCharacter, convertToCamelCase } from '../../lib/utils/text_utility';
import * as constants from '../constants';
import eventHub from '../event_hub';
import issueWarning from '../../vue_shared/components/issue/issue_warning.vue';
import markdownField from '../../vue_shared/components/markdown/field.vue';
import userAvatarLink from '../../vue_shared/components/user_avatar/user_avatar_link.vue';
import loadingButton from '../../vue_shared/components/loading_button.vue';
import noteSignedOutWidget from './note_signed_out_widget.vue';
import discussionLockedWidget from './discussion_locked_widget.vue';
import issuableStateMixin from '../mixins/issuable_state';
import $ from 'jquery';
import { mapActions, mapGetters, mapState } from 'vuex';
import _ from 'underscore';
import Autosize from 'autosize';
import { __, sprintf } from '~/locale';
import Flash from '../../flash';
import Autosave from '../../autosave';
import TaskList from '../../task_list';
import {
capitalizeFirstCharacter,
convertToCamelCase,
} from '../../lib/utils/text_utility';
import * as constants from '../constants';
import eventHub from '../event_hub';
import issueWarning from '../../vue_shared/components/issue/issue_warning.vue';
import markdownField from '../../vue_shared/components/markdown/field.vue';
import userAvatarLink from '../../vue_shared/components/user_avatar/user_avatar_link.vue';
import loadingButton from '../../vue_shared/components/loading_button.vue';
import noteSignedOutWidget from './note_signed_out_widget.vue';
import discussionLockedWidget from './discussion_locked_widget.vue';
import issuableStateMixin from '../mixins/issuable_state';
 
export default {
name: 'CommentForm',
components: {
issueWarning,
noteSignedOutWidget,
discussionLockedWidget,
markdownField,
userAvatarLink,
loadingButton,
export default {
name: 'CommentForm',
components: {
issueWarning,
noteSignedOutWidget,
discussionLockedWidget,
markdownField,
userAvatarLink,
loadingButton,
},
mixins: [issuableStateMixin],
props: {
noteableType: {
type: String,
required: true,
},
mixins: [
issuableStateMixin,
],
props: {
noteableType: {
type: String,
required: true,
},
},
data() {
return {
note: '',
noteType: constants.COMMENT,
isSubmitting: false,
isSubmitButtonDisabled: true,
};
},
computed: {
...mapGetters([
'getCurrentUserLastNote',
'getUserData',
'getNoteableData',
'getNotesData',
'openState',
]),
...mapState(['isToggleStateButtonLoading']),
noteableDisplayName() {
return this.noteableType.replace(/_/g, ' ');
},
data() {
return {
note: '',
noteType: constants.COMMENT,
isSubmitting: false,
isSubmitButtonDisabled: true,
};
isLoggedIn() {
return this.getUserData.id;
},
commentButtonTitle() {
return this.noteType === constants.COMMENT
? 'Comment'
: 'Start discussion';
},
isOpen() {
return (
this.openState === constants.OPENED ||
this.openState === constants.REOPENED
);
},
computed: {
...mapGetters([
'getCurrentUserLastNote',
'getUserData',
'getNoteableData',
'getNotesData',
'openState',
]),
noteableDisplayName() {
return this.noteableType.replace(/_/g, ' ');
},
isLoggedIn() {
return this.getUserData.id;
},
commentButtonTitle() {
return this.noteType === constants.COMMENT ? 'Comment' : 'Start discussion';
},
isOpen() {
return this.openState === constants.OPENED || this.openState === constants.REOPENED;
},
canCreateNote() {
return this.getNoteableData.current_user.can_create_note;
},
issueActionButtonTitle() {
const openOrClose = this.isOpen ? 'close' : 'reopen';
canCreateNote() {
return this.getNoteableData.current_user.can_create_note;
},
issueActionButtonTitle() {
const openOrClose = this.isOpen ? 'close' : 'reopen';
 
if (this.note.length) {
return sprintf(
__('%{actionText} & %{openOrClose} %{noteable}'),
{
actionText: this.commentButtonTitle,
openOrClose,
noteable: this.noteableDisplayName,
},
);
}
if (this.note.length) {
return sprintf(__('%{actionText} & %{openOrClose} %{noteable}'), {
actionText: this.commentButtonTitle,
openOrClose,
noteable: this.noteableDisplayName,
});
}
 
return sprintf(
__('%{openOrClose} %{noteable}'),
{
openOrClose: capitalizeFirstCharacter(openOrClose),
noteable: this.noteableDisplayName,
},
);
},
actionButtonClassNames() {
return {
'btn-reopen': !this.isOpen,
'btn-close': this.isOpen,
'js-note-target-close': this.isOpen,
'js-note-target-reopen': !this.isOpen,
};
},
markdownDocsPath() {
return this.getNotesData.markdownDocsPath;
},
quickActionsDocsPath() {
return this.getNotesData.quickActionsDocsPath;
},
markdownPreviewPath() {
return this.getNoteableData.preview_note_path;
},
author() {
return this.getUserData;
},
canUpdateIssue() {
return this.getNoteableData.current_user.can_update;
},
endpoint() {
return this.getNoteableData.create_note_path;
},
return sprintf(__('%{openOrClose} %{noteable}'), {
openOrClose: capitalizeFirstCharacter(openOrClose),
noteable: this.noteableDisplayName,
});
},
watch: {
note(newNote) {
this.setIsSubmitButtonDisabled(newNote, this.isSubmitting);
},
isSubmitting(newValue) {
this.setIsSubmitButtonDisabled(this.note, newValue);
},
actionButtonClassNames() {
return {
'btn-reopen': !this.isOpen,
'btn-close': this.isOpen,
'js-note-target-close': this.isOpen,
'js-note-target-reopen': !this.isOpen,
};
},
mounted() {
// jQuery is needed here because it is a custom event being dispatched with jQuery.
$(document).on('issuable:change', (e, isClosed) => {
this.toggleIssueLocalState(isClosed ? constants.CLOSED : constants.REOPENED);
});
markdownDocsPath() {
return this.getNotesData.markdownDocsPath;
},
quickActionsDocsPath() {
return this.getNotesData.quickActionsDocsPath;
},
markdownPreviewPath() {
return this.getNoteableData.preview_note_path;
},
author() {
return this.getUserData;
},
canUpdateIssue() {
return this.getNoteableData.current_user.can_update;
},
endpoint() {
return this.getNoteableData.create_note_path;
},
},
watch: {
note(newNote) {
this.setIsSubmitButtonDisabled(newNote, this.isSubmitting);
},
isSubmitting(newValue) {
this.setIsSubmitButtonDisabled(this.note, newValue);
},
},
mounted() {
// jQuery is needed here because it is a custom event being dispatched with jQuery.
$(document).on('issuable:change', (e, isClosed) => {
this.toggleIssueLocalState(
isClosed ? constants.CLOSED : constants.REOPENED,
);
});
 
this.initAutoSave();
this.initTaskList();
this.initAutoSave();
this.initTaskList();
},
methods: {
...mapActions([
'saveNote',
'stopPolling',
'restartPolling',
'removePlaceholderNotes',
'closeIssue',
'reopenIssue',
'toggleIssueLocalState',
'toggleStateButtonLoading',
]),
setIsSubmitButtonDisabled(note, isSubmitting) {
if (!_.isEmpty(note) && !isSubmitting) {
this.isSubmitButtonDisabled = false;
} else {
this.isSubmitButtonDisabled = true;
}
},
methods: {
...mapActions([
'saveNote',
'stopPolling',
'restartPolling',
'removePlaceholderNotes',
'closeIssue',
'reopenIssue',
'toggleIssueLocalState',
]),
setIsSubmitButtonDisabled(note, isSubmitting) {
if (!_.isEmpty(note) && !isSubmitting) {
this.isSubmitButtonDisabled = false;
} else {
this.isSubmitButtonDisabled = true;
}
},
handleSave(withIssueAction) {
this.isSubmitting = true;
handleSave(withIssueAction) {
this.isSubmitting = true;
 
if (this.note.length) {
const noteData = {
endpoint: this.endpoint,
flashContainer: this.$el,
data: {
note: {
noteable_type: this.noteableType,
noteable_id: this.getNoteableData.id,
note: this.note,
},
if (this.note.length) {
const noteData = {
endpoint: this.endpoint,
flashContainer: this.$el,
data: {
note: {
noteable_type: this.noteableType,
noteable_id: this.getNoteableData.id,
note: this.note,
},
};
},
};
 
if (this.noteType === constants.DISCUSSION) {
noteData.data.note.type = constants.DISCUSSION_NOTE;
}
this.note = ''; // Empty textarea while being requested. Repopulate in catch
this.resizeTextarea();
this.stopPolling();
if (this.noteType === constants.DISCUSSION) {
noteData.data.note.type = constants.DISCUSSION_NOTE;
}
 
this.saveNote(noteData)
.then((res) => {
this.isSubmitting = false;
this.restartPolling();
this.note = ''; // Empty textarea while being requested. Repopulate in catch
this.resizeTextarea();
this.stopPolling();
 
if (res.errors) {
if (res.errors.commands_only) {
this.discard();
} else {
Flash(
'Something went wrong while adding your comment. Please try again.',
'alert',
this.$refs.commentForm,
);
}
} else {
this.saveNote(noteData)
.then(res => {
this.enableButton();
this.restartPolling();
if (res.errors) {
if (res.errors.commands_only) {
this.discard();
} else {
Flash(
'Something went wrong while adding your comment. Please try again.',
'alert',
this.$refs.commentForm,
);
}
} else {
this.discard();
}
 
if (withIssueAction) {
this.toggleIssueState();
}
})
.catch(() => {
this.isSubmitting = false;
this.discard(false);
const msg =
`Your comment could not be submitted!
if (withIssueAction) {
this.toggleIssueState();
}
})
.catch(() => {
this.enableButton();
this.discard(false);
const msg = `Your comment could not be submitted!
Please check your network connection and try again.`;
Flash(msg, 'alert', this.$el);
this.note = noteData.data.note.note; // Restore textarea content.
this.removePlaceholderNotes();
});
} else {
this.toggleIssueState();
}
},
enableButton() {
this.isSubmitting = false;
},
toggleIssueState() {
if (this.isOpen) {
this.closeIssue()
.then(() => this.enableButton())
.catch(() => {
this.enableButton();
Flash(
sprintf(
__('Something went wrong while closing the %{issuable}. Please try again later'),
{ issuable: this.noteableDisplayName },
Flash(msg, 'alert', this.$el);
this.note = noteData.data.note.note; // Restore textarea content.
this.removePlaceholderNotes();
});
} else {
this.toggleIssueState();
}
},
enableButton() {
this.isSubmitting = false;
},
toggleIssueState() {
if (this.isOpen) {
this.closeIssue()
.then(() => this.enableButton())
.catch(() => {
this.enableButton();
this.toggleStateButtonLoading(false);
Flash(
sprintf(
__(
'Something went wrong while closing the %{issuable}. Please try again later',
),
);
});
} else {
this.reopenIssue()
.then(() => this.enableButton())
.catch(() => {
this.enableButton();
Flash(
sprintf(
__('Something went wrong while reopening the %{issuable}. Please try again later'),
{ issuable: this.noteableDisplayName },
{ issuable: this.noteableDisplayName },
),
);
});
} else {
this.reopenIssue()
.then(() => this.enableButton())
.catch(() => {
this.enableButton();
this.toggleStateButtonLoading(false);
Flash(
sprintf(
__(
'Something went wrong while reopening the %{issuable}. Please try again later',
),
);
});
}
},
discard(shouldClear = true) {
// `blur` is needed to clear slash commands autocomplete cache if event fired.
// `focus` is needed to remain cursor in the textarea.
this.$refs.textarea.blur();
this.$refs.textarea.focus();
{ issuable: this.noteableDisplayName },
),
);
});
}
},
discard(shouldClear = true) {
// `blur` is needed to clear slash commands autocomplete cache if event fired.
// `focus` is needed to remain cursor in the textarea.
this.$refs.textarea.blur();
this.$refs.textarea.focus();
 
if (shouldClear) {
this.note = '';
this.resizeTextarea();
this.$refs.markdownField.previewMarkdown = false;
}
if (shouldClear) {
this.note = '';
this.resizeTextarea();
this.$refs.markdownField.previewMarkdown = false;
}
 
this.autosave.reset();
},
setNoteType(type) {
this.noteType = type;
},
editCurrentUserLastNote() {
if (this.note === '') {
const lastNote = this.getCurrentUserLastNote;
this.autosave.reset();
},
setNoteType(type) {
this.noteType = type;
},
editCurrentUserLastNote() {
if (this.note === '') {
const lastNote = this.getCurrentUserLastNote;
 
if (lastNote) {
eventHub.$emit('enterEditMode', {
noteId: lastNote.id,
});
}
if (lastNote) {
eventHub.$emit('enterEditMode', {
noteId: lastNote.id,
});
}
},
initAutoSave() {
if (this.isLoggedIn) {
const noteableType = capitalizeFirstCharacter(convertToCamelCase(this.noteableType));
}
},
initAutoSave() {
if (this.isLoggedIn) {
const noteableType = capitalizeFirstCharacter(
convertToCamelCase(this.noteableType),
);
 
this.autosave = new Autosave(
$(this.$refs.textarea),
['Note', noteableType, this.getNoteableData.id],
);
}
},
initTaskList() {
return new TaskList({
dataType: 'note',
fieldName: 'note',
selector: '.notes',
});
},
resizeTextarea() {
this.$nextTick(() => {
Autosize.update(this.$refs.textarea);
});
},
this.autosave = new Autosave($(this.$refs.textarea), [
'Note',
noteableType,
this.getNoteableData.id,
]);
}
},
initTaskList() {
return new TaskList({
dataType: 'note',
fieldName: 'note',
selector: '.notes',
});
},
};
resizeTextarea() {
this.$nextTick(() => {
Autosize.update(this.$refs.textarea);
});
},
},
};
</script>
 
<template>
Loading
Loading
@@ -419,13 +432,13 @@ append-right-10 comment-type-dropdown js-comment-type-dropdown droplab-dropdown"
 
<loading-button
v-if="canUpdateIssue"
:loading="isSubmitting"
:loading="isToggleStateButtonLoading"
@click="handleSave(true)"
:container-class="[
actionButtonClassNames,
'btn btn-comment btn-comment-and-close js-action-button'
]"
:disabled="isSubmitting"
:disabled="isToggleStateButtonLoading || isSubmitting"
:label="issueActionButtonTitle"
/>
 
Loading
Loading
<script>
import ClipboardButton from '~/vue_shared/components/clipboard_button.vue';
import Icon from '~/vue_shared/components/icon.vue';
import ClipboardButton from '~/vue_shared/components/clipboard_button.vue';
import Icon from '~/vue_shared/components/icon.vue';
 
export default {
components: {
ClipboardButton,
Icon,
export default {
components: {
ClipboardButton,
Icon,
},
props: {
diffFile: {
type: Object,
required: true,
},
props: {
diffFile: {
type: Object,
required: true,
},
},
computed: {
titleTag() {
return this.diffFile.discussionPath ? 'a' : 'span';
},
computed: {
titleTag() {
return this.diffFile.discussionPath ? 'a' : 'span';
},
},
};
},
};
</script>
 
<template>
Loading
Loading
<script>
import $ from 'jquery';
import syntaxHighlight from '~/syntax_highlight';
import imageDiffHelper from '~/image_diff/helpers/index';
import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils';
import DiffFileHeader from './diff_file_header.vue';
import $ from 'jquery';
import syntaxHighlight from '~/syntax_highlight';
import imageDiffHelper from '~/image_diff/helpers/index';
import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils';
import DiffFileHeader from './diff_file_header.vue';
 
export default {
components: {
DiffFileHeader,
export default {
components: {
DiffFileHeader,
},
props: {
discussion: {
type: Object,
required: true,
},
props: {
discussion: {
type: Object,
required: true,
},
},
computed: {
isImageDiff() {
return !this.diffFile.text;
},
computed: {
isImageDiff() {
return !this.diffFile.text;
},
diffFileClass() {
const { text } = this.diffFile;
return text ? 'text-file' : 'js-image-file';
},
diffRows() {
return $(this.discussion.truncatedDiffLines);
},
diffFile() {
return convertObjectPropsToCamelCase(this.discussion.diffFile);
},
imageDiffHtml() {
return this.discussion.imageDiffHtml;
},
diffFileClass() {
const { text } = this.diffFile;
return text ? 'text-file' : 'js-image-file';
},
mounted() {
if (this.isImageDiff) {
const canCreateNote = false;
const renderCommentBadge = true;
imageDiffHelper.initImageDiff(this.$refs.fileHolder, canCreateNote, renderCommentBadge);
} else {
const fileHolder = $(this.$refs.fileHolder);
this.$nextTick(() => {
syntaxHighlight(fileHolder);
});
}
diffRows() {
return $(this.discussion.truncatedDiffLines);
},
methods: {
rowTag(html) {
return html.outerHTML ? 'tr' : 'template';
},
diffFile() {
return convertObjectPropsToCamelCase(this.discussion.diffFile);
},
};
imageDiffHtml() {
return this.discussion.imageDiffHtml;
},
},
mounted() {
if (this.isImageDiff) {
const canCreateNote = false;
const renderCommentBadge = true;
imageDiffHelper.initImageDiff(
this.$refs.fileHolder,
canCreateNote,
renderCommentBadge,
);
} else {
const fileHolder = $(this.$refs.fileHolder);
this.$nextTick(() => {
syntaxHighlight(fileHolder);
});
}
},
methods: {
rowTag(html) {
return html.outerHTML ? 'tr' : 'template';
},
},
};
</script>
 
<template>
Loading
Loading
<script>
import { mapGetters } from 'vuex';
import resolveSvg from 'icons/_icon_resolve_discussion.svg';
import resolvedSvg from 'icons/_icon_status_success_solid.svg';
import mrIssueSvg from 'icons/_icon_mr_issue.svg';
import nextDiscussionSvg from 'icons/_next_discussion.svg';
import { pluralize } from '../../lib/utils/text_utility';
import { scrollToElement } from '../../lib/utils/common_utils';
import tooltip from '../../vue_shared/directives/tooltip';
import { mapGetters } from 'vuex';
import resolveSvg from 'icons/_icon_resolve_discussion.svg';
import resolvedSvg from 'icons/_icon_status_success_solid.svg';
import mrIssueSvg from 'icons/_icon_mr_issue.svg';
import nextDiscussionSvg from 'icons/_next_discussion.svg';
import { pluralize } from '../../lib/utils/text_utility';
import { scrollToElement } from '../../lib/utils/common_utils';
import tooltip from '../../vue_shared/directives/tooltip';
 
export default {
directives: {
tooltip,
export default {
directives: {
tooltip,
},
computed: {
...mapGetters([
'getUserData',
'getNoteableData',
'discussionCount',
'unresolvedDiscussions',
'resolvedDiscussionCount',
]),
isLoggedIn() {
return this.getUserData.id;
},
computed: {
...mapGetters([
'getUserData',
'getNoteableData',
'discussionCount',
'unresolvedDiscussions',
'resolvedDiscussionCount',
]),
isLoggedIn() {
return this.getUserData.id;
},
hasNextButton() {
return this.isLoggedIn && !this.allResolved;
},
countText() {
return pluralize('discussion', this.discussionCount);
},
allResolved() {
return this.resolvedDiscussionCount === this.discussionCount;
},
resolveAllDiscussionsIssuePath() {
return this.getNoteableData.create_issue_to_resolve_discussions_path;
},
firstUnresolvedDiscussionId() {
const item = this.unresolvedDiscussions[0] || {};
return item.id;
},
hasNextButton() {
return this.isLoggedIn && !this.allResolved;
},
countText() {
return pluralize('discussion', this.discussionCount);
},
allResolved() {
return this.resolvedDiscussionCount === this.discussionCount;
},
created() {
this.resolveSvg = resolveSvg;
this.resolvedSvg = resolvedSvg;
this.mrIssueSvg = mrIssueSvg;
this.nextDiscussionSvg = nextDiscussionSvg;
resolveAllDiscussionsIssuePath() {
return this.getNoteableData.create_issue_to_resolve_discussions_path;
},
firstUnresolvedDiscussionId() {
const item = this.unresolvedDiscussions[0] || {};
return item.id;
},
methods: {
jumpToFirstDiscussion() {
const el = document.querySelector(`[data-discussion-id="${this.firstUnresolvedDiscussionId}"]`);
const activeTab = window.mrTabs.currentAction;
},
created() {
this.resolveSvg = resolveSvg;
this.resolvedSvg = resolvedSvg;
this.mrIssueSvg = mrIssueSvg;
this.nextDiscussionSvg = nextDiscussionSvg;
},
methods: {
jumpToFirstDiscussion() {
const el = document.querySelector(
`[data-discussion-id="${this.firstUnresolvedDiscussionId}"]`,
);
const activeTab = window.mrTabs.currentAction;
 
if (activeTab === 'commits' || activeTab === 'pipelines') {
window.mrTabs.activateTab('show');
}
if (activeTab === 'commits' || activeTab === 'pipelines') {
window.mrTabs.activateTab('show');
}
 
if (el) {
scrollToElement(el);
}
},
if (el) {
scrollToElement(el);
}
},
};
},
};
</script>
 
<template>
Loading
Loading
<script>
import Icon from '~/vue_shared/components/icon.vue';
import Issuable from '~/vue_shared/mixins/issuable';
import Icon from '~/vue_shared/components/icon.vue';
import Issuable from '~/vue_shared/mixins/issuable';
 
export default {
components: {
Icon,
},
mixins: [
Issuable,
],
};
export default {
components: {
Icon,
},
mixins: [Issuable],
};
</script>
 
<template>
Loading
Loading
<script>
import { mapGetters } from 'vuex';
import emojiSmiling from 'icons/_emoji_slightly_smiling_face.svg';
import emojiSmile from 'icons/_emoji_smile.svg';
import emojiSmiley from 'icons/_emoji_smiley.svg';
import editSvg from 'icons/_icon_pencil.svg';
import resolveDiscussionSvg from 'icons/_icon_resolve_discussion.svg';
import resolvedDiscussionSvg from 'icons/_icon_status_success_solid.svg';
import ellipsisSvg from 'icons/_ellipsis_v.svg';
import loadingIcon from '~/vue_shared/components/loading_icon.vue';
import tooltip from '~/vue_shared/directives/tooltip';
import { mapGetters } from 'vuex';
import emojiSmiling from 'icons/_emoji_slightly_smiling_face.svg';
import emojiSmile from 'icons/_emoji_smile.svg';
import emojiSmiley from 'icons/_emoji_smiley.svg';
import editSvg from 'icons/_icon_pencil.svg';
import resolveDiscussionSvg from 'icons/_icon_resolve_discussion.svg';
import resolvedDiscussionSvg from 'icons/_icon_status_success_solid.svg';
import ellipsisSvg from 'icons/_ellipsis_v.svg';
import loadingIcon from '~/vue_shared/components/loading_icon.vue';
import tooltip from '~/vue_shared/directives/tooltip';
 
export default {
name: 'NoteActions',
directives: {
tooltip,
},
components: {
loadingIcon,
},
props: {
authorId: {
type: Number,
required: true,
},
noteId: {
type: Number,
required: true,
},
accessLevel: {
type: String,
required: false,
default: '',
},
reportAbusePath: {
type: String,
required: true,
},
canEdit: {
type: Boolean,
required: true,
},
canDelete: {
type: Boolean,
required: true,
},
resolvable: {
type: Boolean,
required: false,
default: false,
},
isResolved: {
type: Boolean,
required: false,
default: false,
},
isResolving: {
type: Boolean,
required: false,
default: false,
},
resolvedBy: {
type: Object,
required: false,
default: () => ({}),
},
canReportAsAbuse: {
type: Boolean,
required: true,
},
},
computed: {
...mapGetters([
'getUserDataByProp',
]),
shouldShowActionsDropdown() {
return this.currentUserId && (this.canEdit || this.canReportAsAbuse);
},
canAddAwardEmoji() {
return this.currentUserId;
},
isAuthoredByCurrentUser() {
return this.authorId === this.currentUserId;
},
currentUserId() {
return this.getUserDataByProp('id');
},
resolveButtonTitle() {
let title = 'Mark as resolved';
export default {
name: 'NoteActions',
directives: {
tooltip,
},
components: {
loadingIcon,
},
props: {
authorId: {
type: Number,
required: true,
},
noteId: {
type: Number,
required: true,
},
accessLevel: {
type: String,
required: false,
default: '',
},
reportAbusePath: {
type: String,
required: true,
},
canEdit: {
type: Boolean,
required: true,
},
canDelete: {
type: Boolean,
required: true,
},
resolvable: {
type: Boolean,
required: false,
default: false,
},
isResolved: {
type: Boolean,
required: false,
default: false,
},
isResolving: {
type: Boolean,
required: false,
default: false,
},
resolvedBy: {
type: Object,
required: false,
default: () => ({}),
},
canReportAsAbuse: {
type: Boolean,
required: true,
},
},
computed: {
...mapGetters(['getUserDataByProp']),
shouldShowActionsDropdown() {
return this.currentUserId && (this.canEdit || this.canReportAsAbuse);
},
canAddAwardEmoji() {
return this.currentUserId;
},
isAuthoredByCurrentUser() {
return this.authorId === this.currentUserId;
},
currentUserId() {
return this.getUserDataByProp('id');
},
resolveButtonTitle() {
let title = 'Mark as resolved';
 
if (this.resolvedBy) {
title = `Resolved by ${this.resolvedBy.name}`;
}
if (this.resolvedBy) {
title = `Resolved by ${this.resolvedBy.name}`;
}
 
return title;
},
},
created() {
this.emojiSmiling = emojiSmiling;
this.emojiSmile = emojiSmile;
this.emojiSmiley = emojiSmiley;
this.editSvg = editSvg;
this.ellipsisSvg = ellipsisSvg;
this.resolveDiscussionSvg = resolveDiscussionSvg;
this.resolvedDiscussionSvg = resolvedDiscussionSvg;
},
methods: {
onEdit() {
this.$emit('handleEdit');
},
onDelete() {
this.$emit('handleDelete');
},
onResolve() {
this.$emit('handleResolve');
},
},
};
return title;
},
},
created() {
this.emojiSmiling = emojiSmiling;
this.emojiSmile = emojiSmile;
this.emojiSmiley = emojiSmiley;
this.editSvg = editSvg;
this.ellipsisSvg = ellipsisSvg;
this.resolveDiscussionSvg = resolveDiscussionSvg;
this.resolvedDiscussionSvg = resolvedDiscussionSvg;
},
methods: {
onEdit() {
this.$emit('handleEdit');
},
onDelete() {
this.$emit('handleDelete');
},
onResolve() {
this.$emit('handleResolve');
},
},
};
</script>
 
<template>
Loading
Loading
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment