Commit a538b0da authored by Lin Jen-Shin's avatar Lin Jen-Shin

Merge remote-tracking branch 'upstream/master' into 27762-add-default-artifacts-expiration

* upstream/master: (234 commits)
  Improve performance of User Agent Detail
  Fix some grammar in the API docs
  Remove shared example for pagination
  API: Use POST to (un)block a user
  API: Moved `DELETE /projects/:id/star` to `POST /projects/:id/unstar`
  Use grape validation for dates
  Change wording for LDAP doc that was moved to a new location
  API: Remove `DELETE projects/:id/deploy_keys/:key_id/disable`
  Download snippets with LF line-endings by default
  utilize pre-minified Vue in production since no CJS distribution is available
  Prevent project team from being truncated too early during project destruction
  loading icon sometimes toggled alongside MR pipeline contents
  fix failed spec because haml_lint
  fix incorrect sidekiq concurrency count in admin background page
  exclude rpc_pipefs from system disc info
  Fix wrong line ending [ci-skip]
  fix overlooked window binding in spec files
  remove imports loader
  replace implicit this == window with explicit binding
  Todo done clicking is kind of unusable.
  ...
parents eede4ab1 9fe863f4
### Background:
(Include problem, use cases, benefits, and/or goals)
**What questions are you trying to answer?**
**Are you looking to verify an existing hypothesis or uncover new issues you should be exploring?**
**What is the backstory of this project and how does it impact the approach?**
**What do you already know about the areas you are exploring?**
**What does success look like at the end of the project?**
### Links / references:
/label ~"UX research"
...@@ -2,6 +2,22 @@ ...@@ -2,6 +2,22 @@
documentation](doc/development/changelog.md) for instructions on adding your own documentation](doc/development/changelog.md) for instructions on adding your own
entry. entry.
## 8.16.6 (2017-02-17)
- API: Fix file downloading. !0 (8267)
- Reduce hits to LDAP on Git HTTP auth by reordering auth mechanisms. !8752
- Fix filtered search user autocomplete for gitlab instances that are hosted on a subdirectory. !8891
- Fix wrong call to ProjectCacheWorker.perform. !8910
- Remove unnecessary queries for .atom and .json in Dashboard::ProjectsController#index. !8956
- Fix broken anchor links when special characters are used. !8961 (Andrey Krivko)
- Do not display deploy keys in user's own ssh keys list. !9024
- Show merge errors in merge request widget. !9229
- Don't delete assigned MRs/issues when user is deleted.
- backport of EE fix !954.
- Refresh authorizations when transferring projects.
- Don't use backup Active Record connections for Sidekiq.
- Check public snippets for spam.
## 8.16.5 (2017-02-14) ## 8.16.5 (2017-02-14)
- Patch Asciidocs rendering to block XSS. - Patch Asciidocs rendering to block XSS.
...@@ -181,6 +197,10 @@ entry. ...@@ -181,6 +197,10 @@ entry.
- Add margin to markdown math blocks. - Add margin to markdown math blocks.
- Add hover state to MR comment reply button. - Add hover state to MR comment reply button.
## 8.15.7 (2017-02-15)
- No changes.
## 8.15.6 (2017-02-14) ## 8.15.6 (2017-02-14)
- Patch Asciidocs rendering to block XSS. - Patch Asciidocs rendering to block XSS.
...@@ -451,6 +471,10 @@ entry. ...@@ -451,6 +471,10 @@ entry.
- Whitelist next project names: help, ci, admin, search. !8227 - Whitelist next project names: help, ci, admin, search. !8227
- Adds back CSS for progress-bars. !8237 - Adds back CSS for progress-bars. !8237
## 8.14.10 (2017-02-15)
- No changes.
## 8.14.9 (2017-02-14) ## 8.14.9 (2017-02-14)
- Patch Asciidocs rendering to block XSS. - Patch Asciidocs rendering to block XSS.
......
...@@ -93,18 +93,20 @@ Please see the [UX Guide for GitLab]. ...@@ -93,18 +93,20 @@ Please see the [UX Guide for GitLab].
### Retrospective ### Retrospective
After each release (usually on the 22nd of each month), we have a retrospective After each release, we have a retrospective call where we discuss what went well,
call where we discuss what went well, what went wrong, and what we can improve what went wrong, and what we can improve for the next release. The
for the next release. The [retrospective notes] are public and you are invited [retrospective notes] are public and you are invited to comment on them.
to comment them. If you're interested, you can even join the
If you're interested, you can even join the [retrospective call][retro-kickoff-call]. [retrospective call][retro-kickoff-call], on the first working day after the
22nd at 6pm CET / 9am PST.
### Kickoff ### Kickoff
Before working on the next release (usually on the 8th of each month), we have a Before working on the next release, we have a
kickoff call to explain what we expect to ship in the next release. The kickoff call to explain what we expect to ship in the next release. The
[kickoff notes] are public and you are invited to comment them. [kickoff notes] are public and you are invited to comment on them.
If you're interested, you can even join the [kickoff call][retro-kickoff-call]. If you're interested, you can even join the [kickoff call][retro-kickoff-call],
on the first working day after the 7th at 6pm CET / 9am PST..
[retrospective notes]: https://docs.google.com/document/d/1nEkM_7Dj4bT21GJy0Ut3By76FZqCfLBmFQNVThmW2TY/edit?usp=sharing [retrospective notes]: https://docs.google.com/document/d/1nEkM_7Dj4bT21GJy0Ut3By76FZqCfLBmFQNVThmW2TY/edit?usp=sharing
[kickoff notes]: https://docs.google.com/document/d/1ElPkZ90A8ey_iOkTvUs_ByMlwKK6NAB2VOK5835wYK0/edit?usp=sharing [kickoff notes]: https://docs.google.com/document/d/1ElPkZ90A8ey_iOkTvUs_ByMlwKK6NAB2VOK5835wYK0/edit?usp=sharing
......
...@@ -61,4 +61,4 @@ ...@@ -61,4 +61,4 @@
return Admin; return Admin;
})(); })();
}).call(this); }).call(window);
...@@ -147,4 +147,4 @@ ...@@ -147,4 +147,4 @@
}; };
window.Api = Api; window.Api = Api;
}).call(this); }).call(window);
...@@ -101,11 +101,6 @@ require('es6-promise').polyfill(); ...@@ -101,11 +101,6 @@ require('es6-promise').polyfill();
} }
}); });
$('.nav-sidebar').niceScroll({
cursoropacitymax: '0.4',
cursorcolor: '#FFF',
cursorborder: '1px solid #FFF'
});
$('.js-select-on-focus').on('focusin', function () { $('.js-select-on-focus').on('focusin', function () {
return $(this).select().one('mouseup', function (e) { return $(this).select().one('mouseup', function (e) {
return e.preventDefault(); return e.preventDefault();
...@@ -245,9 +240,7 @@ require('es6-promise').polyfill(); ...@@ -245,9 +240,7 @@ require('es6-promise').polyfill();
}); });
gl.awardsHandler = new AwardsHandler(); gl.awardsHandler = new AwardsHandler();
new Aside(); new Aside();
// bind sidebar events
new gl.Sidebar();
gl.utils.initTimeagoTimeout(); gl.utils.initTimeagoTimeout();
}); });
}).call(this); }).call(window);
...@@ -22,4 +22,4 @@ ...@@ -22,4 +22,4 @@
return Aside; return Aside;
})(); })();
}).call(this); }).call(window);
...@@ -59,4 +59,4 @@ ...@@ -59,4 +59,4 @@
return Autosave; return Autosave;
})(); })();
}).call(this); }).call(window);
...@@ -377,4 +377,4 @@ var emojiAliases = require('emoji-aliases'); ...@@ -377,4 +377,4 @@ var emojiAliases = require('emoji-aliases');
return AwardsHandler; return AwardsHandler;
})(); })();
}).call(this); }).call(window);
...@@ -25,4 +25,4 @@ var autosize = require('vendor/autosize'); ...@@ -25,4 +25,4 @@ var autosize = require('vendor/autosize');
autosize.update($fields); autosize.update($fields);
return $fields.css('resize', 'vertical'); return $fields.css('resize', 'vertical');
}); });
}).call(this); }).call(window);
...@@ -23,4 +23,4 @@ ...@@ -23,4 +23,4 @@
return e.preventDefault(); return e.preventDefault();
}); });
}); });
}).call(this); }).call(window);
...@@ -74,4 +74,4 @@ require('../extensions/jquery'); ...@@ -74,4 +74,4 @@ require('../extensions/jquery');
return $this.tooltip('hide'); return $this.tooltip('hide');
}); });
}); });
}).call(this); }).call(window);
...@@ -59,4 +59,4 @@ require('../extensions/jquery'); ...@@ -59,4 +59,4 @@ require('../extensions/jquery');
return hideOrShowHelpBlock($form); return hideOrShowHelpBlock($form);
}); });
}); });
}).call(this); }).call(window);
...@@ -63,4 +63,4 @@ ...@@ -63,4 +63,4 @@
return BlobFileDropzone; return BlobFileDropzone;
})(); })();
}).call(this); }).call(window);
...@@ -20,4 +20,4 @@ require('./template_selector'); ...@@ -20,4 +20,4 @@ require('./template_selector');
return BlobGitignoreSelector; return BlobGitignoreSelector;
})(gl.TemplateSelector); })(gl.TemplateSelector);
}).call(this); }).call(window);
...@@ -23,4 +23,4 @@ ...@@ -23,4 +23,4 @@
return BlobGitignoreSelectors; return BlobGitignoreSelectors;
})(); })();
}).call(this); }).call(window);
...@@ -25,4 +25,4 @@ require('./template_selector'); ...@@ -25,4 +25,4 @@ require('./template_selector');
return BlobLicenseSelector; return BlobLicenseSelector;
})(gl.TemplateSelector); })(gl.TemplateSelector);
}).call(this); }).call(window);
...@@ -12,4 +12,4 @@ require('./edit_blob'); ...@@ -12,4 +12,4 @@ require('./edit_blob');
var blob = new EditBlob(url, $('.js-edit-blob-form').data('blob-language')); var blob = new EditBlob(url, $('.js-edit-blob-form').data('blob-language'));
new NewCommitForm($('.js-edit-blob-form')); new NewCommitForm($('.js-edit-blob-form'));
}); });
}).call(this); }).call(window);
...@@ -85,4 +85,4 @@ ...@@ -85,4 +85,4 @@
return EditBlob; return EditBlob;
})(); })();
}).call(this); }).call(window);
...@@ -95,7 +95,7 @@ $(() => { ...@@ -95,7 +95,7 @@ $(() => {
}, },
computed: { computed: {
disabled() { disabled() {
return Store.shouldAddBlankState(); return !this.store.lists.filter(list => list.type !== 'blank' && list.type !== 'done').length;
}, },
}, },
template: ` template: `
......
...@@ -69,4 +69,4 @@ ...@@ -69,4 +69,4 @@
})(this)); })(this));
window.Breakpoints = Breakpoints; window.Breakpoints = Breakpoints;
}).call(this); }).call(window);
...@@ -31,4 +31,4 @@ ...@@ -31,4 +31,4 @@
} }
}); });
}); });
}).call(this); }).call(window);
...@@ -275,4 +275,4 @@ ...@@ -275,4 +275,4 @@
return Build; return Build;
})(); })();
}).call(this); }).call(window);
...@@ -23,4 +23,4 @@ ...@@ -23,4 +23,4 @@
return BuildArtifacts; return BuildArtifacts;
})(); })();
}).call(this); }).call(window);
...@@ -11,4 +11,4 @@ ...@@ -11,4 +11,4 @@
return Commit; return Commit;
})(); })();
}).call(this); }).call(window);
...@@ -11,4 +11,4 @@ ...@@ -11,4 +11,4 @@
return CommitFile; return CommitFile;
})(); })();
}).call(this); }).call(window);
...@@ -173,4 +173,4 @@ ...@@ -173,4 +173,4 @@
return ImageFile; return ImageFile;
})(); })();
}).call(this); }).call(window);
...@@ -4,6 +4,7 @@ ...@@ -4,6 +4,7 @@
* *
* Used to store the Pipelines rendered in the commit view in the pipelines table. * Used to store the Pipelines rendered in the commit view in the pipelines table.
*/ */
require('../../vue_realtime_listener');
class PipelinesStore { class PipelinesStore {
constructor() { constructor() {
...@@ -24,7 +25,7 @@ class PipelinesStore { ...@@ -24,7 +25,7 @@ class PipelinesStore {
* update the time to show how long as passed. * update the time to show how long as passed.
* *
*/ */
startTimeAgoLoops() { static startTimeAgoLoops() {
const startTimeLoops = () => { const startTimeLoops = () => {
this.timeLoopInterval = setInterval(() => { this.timeLoopInterval = setInterval(() => {
this.$children[0].$children.reduce((acc, component) => { this.$children[0].$children.reduce((acc, component) => {
...@@ -44,7 +45,4 @@ class PipelinesStore { ...@@ -44,7 +45,4 @@ class PipelinesStore {
} }
} }
window.gl = window.gl || {}; module.exports = PipelinesStore;
gl.commits = gl.commits || {};
gl.commits.pipelines = gl.commits.pipelines || {};
gl.commits.pipelines.PipelinesStore = PipelinesStore;
...@@ -6,9 +6,8 @@ window.Vue.use(require('vue-resource')); ...@@ -6,9 +6,8 @@ window.Vue.use(require('vue-resource'));
require('../../lib/utils/common_utils'); require('../../lib/utils/common_utils');
require('../../vue_shared/vue_resource_interceptor'); require('../../vue_shared/vue_resource_interceptor');
require('../../vue_shared/components/pipelines_table'); require('../../vue_shared/components/pipelines_table');
require('../../vue_realtime_listener/index');
require('./pipelines_service'); require('./pipelines_service');
require('./pipelines_store'); const PipelineStore = require('./pipelines_store');
/** /**
* *
...@@ -41,7 +40,7 @@ require('./pipelines_store'); ...@@ -41,7 +40,7 @@ require('./pipelines_store');
data() { data() {
const pipelinesTableData = document.querySelector('#commit-pipeline-table-view').dataset; const pipelinesTableData = document.querySelector('#commit-pipeline-table-view').dataset;
const svgsData = document.querySelector('.pipeline-svgs').dataset; const svgsData = document.querySelector('.pipeline-svgs').dataset;
const store = new gl.commits.pipelines.PipelinesStore(); const store = new PipelineStore();
// Transform svgs DOMStringMap to a plain Object. // Transform svgs DOMStringMap to a plain Object.
const svgsObject = gl.utils.DOMStringMapToObject(svgsData); const svgsObject = gl.utils.DOMStringMapToObject(svgsData);
...@@ -71,7 +70,6 @@ require('./pipelines_store'); ...@@ -71,7 +70,6 @@ require('./pipelines_store');
.then(response => response.json()) .then(response => response.json())
.then((json) => { .then((json) => {
this.store.storePipelines(json); this.store.storePipelines(json);
this.store.startTimeAgoLoops.call(this, Vue);
this.isLoading = false; this.isLoading = false;
}) })
.catch(() => { .catch(() => {
...@@ -80,6 +78,12 @@ require('./pipelines_store'); ...@@ -80,6 +78,12 @@ require('./pipelines_store');
}); });
}, },
beforeUpdate() {
if (this.state.pipelines.length && this.$children) {
PipelineStore.startTimeAgoLoops.call(this, Vue);
}
},
template: ` template: `
<div class="pipelines"> <div class="pipelines">
<div class="realtime-loading" v-if="isLoading"> <div class="realtime-loading" v-if="isLoading">
......
...@@ -65,4 +65,4 @@ ...@@ -65,4 +65,4 @@
return CommitsList; return CommitsList;
})(); })();
}).call(this); }).call(window);
...@@ -88,4 +88,4 @@ ...@@ -88,4 +88,4 @@
return Compare; return Compare;
})(); })();
}).call(this); }).call(window);
...@@ -66,4 +66,4 @@ ...@@ -66,4 +66,4 @@
return CompareAutocomplete; return CompareAutocomplete;
})(); })();
}).call(this); }).call(window);
...@@ -28,4 +28,4 @@ ...@@ -28,4 +28,4 @@
return ConfirmDangerModal; return ConfirmDangerModal;
})(); })();
}).call(this); }).call(window);
...@@ -46,4 +46,4 @@ window.Clipboard = require('vendor/clipboard'); ...@@ -46,4 +46,4 @@ window.Clipboard = require('vendor/clipboard');
clipboard.on('success', genericSuccess); clipboard.on('success', genericSuccess);
return clipboard.on('error', genericError); return clipboard.on('error', genericError);
}); });
}).call(this); }).call(window);
...@@ -97,7 +97,7 @@ $(() => { ...@@ -97,7 +97,7 @@ $(() => {
} }
this.isLoadingStage = true; this.isLoadingStage = true;
cycleAnalyticsStore.setStageEvents([]); cycleAnalyticsStore.setStageEvents([], stage);
cycleAnalyticsStore.setActiveStage(stage); cycleAnalyticsStore.setActiveStage(stage);
cycleAnalyticsService cycleAnalyticsService
...@@ -107,7 +107,7 @@ $(() => { ...@@ -107,7 +107,7 @@ $(() => {
}) })
.done((response) => { .done((response) => {
this.isEmptyStage = !response.events.length; this.isEmptyStage = !response.events.length;
cycleAnalyticsStore.setStageEvents(response.events); cycleAnalyticsStore.setStageEvents(response.events, stage);
}) })
.error(() => { .error(() => {
this.isEmptyStage = true; this.isEmptyStage = true;
......
/* eslint-disable no-param-reassign */ /* eslint-disable no-param-reassign */
require('../lib/utils/text_utility');
const DEFAULT_EVENT_OBJECTS = require('./default_event_objects');
((global) => { ((global) => {
global.cycleAnalytics = global.cycleAnalytics || {}; global.cycleAnalytics = global.cycleAnalytics || {};
...@@ -34,11 +38,12 @@ ...@@ -34,11 +38,12 @@
}); });
newData.stages.forEach((item) => { newData.stages.forEach((item) => {
const stageName = item.title.toLowerCase(); const stageSlug = gl.text.dasherize(item.title.toLowerCase());
item.active = false; item.active = false;
item.isUserAllowed = data.permissions[stageName]; item.isUserAllowed = data.permissions[stageSlug];
item.emptyStageText = EMPTY_STAGE_TEXTS[stageName]; item.emptyStageText = EMPTY_STAGE_TEXTS[stageSlug];
item.component = `stage-${stageName}-component`; item.component = `stage-${stageSlug}-component`;
item.slug = stageSlug;
}); });
newData.analytics = data; newData.analytics = data;
return newData; return newData;
...@@ -58,31 +63,33 @@ ...@@ -58,31 +63,33 @@
this.deactivateAllStages(); this.deactivateAllStages();
stage.active = true; stage.active = true;
}, },
setStageEvents(events) { setStageEvents(events, stage) {
this.state.events = this.decorateEvents(events); this.state.events = this.decorateEvents(events, stage);
}, },
decorateEvents(events) { decorateEvents(events, stage) {
const newEvents = []; const newEvents = [];
events.forEach((item) => { events.forEach((item) => {
if (!item) return; if (!item) return;
item.totalTime = item.total_time; const eventItem = Object.assign({}, DEFAULT_EVENT_OBJECTS[stage.slug], item);
item.author.webUrl = item.author.web_url;
item.author.avatarUrl = item.author.avatar_url; eventItem.totalTime = eventItem.total_time;
eventItem.author.webUrl = eventItem.author.web_url;
eventItem.author.avatarUrl = eventItem.author.avatar_url;
if (item.created_at) item.createdAt = item.created_at; if (eventItem.created_at) eventItem.createdAt = eventItem.created_at;
if (item.short_sha) item.shortSha = item.short_sha; if (eventItem.short_sha) eventItem.shortSha = eventItem.short_sha;
if (item.commit_url) item.commitUrl = item.commit_url; if (eventItem.commit_url) eventItem.commitUrl = eventItem.commit_url;
delete item.author.web_url; delete eventItem.author.web_url;
delete item.author.avatar_url; delete eventItem.author.avatar_url;
delete item.total_time; delete eventItem.total_time;
delete item.created_at; delete eventItem.created_at;
delete item.short_sha; delete eventItem.short_sha;
delete item.commit_url; delete eventItem.commit_url;
newEvents.push(item); newEvents.push(eventItem);
}); });
return newEvents; return newEvents;
......
module.exports = {
issue: {
created_at: '',
url: '',
iid: '',
title: '',
total_time: {},
author: {
avatar_url: '',
id: '',
name: '',
web_url: '',
},
},
plan: {
title: '',
commit_url: '',
short_sha: '',
total_time: {},
author: {
name: '',
id: '',
avatar_url: '',
web_url: '',
},
},
code: {
title: '',
iid: '',
created_at: '',
url: '',
total_time: {},
author: {
name: '',
id: '',
avatar_url: '',
web_url: '',
},
},
test: {
name: '',
id: '',
date: '',
url: '',
short_sha: '',
commit_url: '',
total_time: {},
branch: {
name: '',
url: '',
},
},
review: {
title: '',
iid: '',
created_at: '',
url: '',
state: '',
total_time: {},
author: {
name: '',
id: '',
avatar_url: '',
web_url: '',
},
},
staging: {
id: '',
short_sha: '',
date: '',
url: '',
commit_url: '',
total_time: {},
author: {
name: '',
id: '',
avatar_url: '',
web_url: '',
},
branch: {
name: '',
url: '',
},
},
production: {
title: '',
created_at: '',
url: '',
iid: '',
total_time: {},
author: {
name: '',
id: '',
avatar_url: '',
web_url: '',
},
},
};
...@@ -118,6 +118,7 @@ const ShortcutsBlob = require('./shortcuts_blob'); ...@@ -118,6 +118,7 @@ const ShortcutsBlob = require('./shortcuts_blob');
new gl.IssuableTemplateSelectors(); new gl.IssuableTemplateSelectors();
break; break;
case 'projects:merge_requests:new': case 'projects:merge_requests:new':
case 'projects:merge_requests:new_diffs':
case 'projects:merge_requests:edit': case 'projects:merge_requests:edit':
new gl.Diff(); new gl.Diff();
shortcut_handler = new ShortcutsNavigation(); shortcut_handler = new ShortcutsNavigation();
...@@ -382,4 +383,4 @@ const ShortcutsBlob = require('./shortcuts_blob'); ...@@ -382,4 +383,4 @@ const ShortcutsBlob = require('./shortcuts_blob');
return Dispatcher; return Dispatcher;
})(); })();
}).call(this); }).call(window);
...@@ -216,4 +216,4 @@ require('./preview_markdown'); ...@@ -216,4 +216,4 @@ require('./preview_markdown');
return DropzoneInput; return DropzoneInput;
})(); })();
}).call(this); }).call(window);
/* global Vue */ const Vue = require('vue');
window.Vue = require('vue'); module.exports = Vue.component('actions-component', {
props: {
(() => { actions: {
window.gl = window.gl || {}; type: Array,
window.gl.environmentsList = window.gl.environmentsList || {}; required: false,
default: () => [],
gl.environmentsList.ActionsComponent = Vue.component('actions-component', {
props: {
actions: {
type: Array,
required: false,
default: () => [],
},
playIconSvg: {
type: String,
required: false,
},
}, },
template: ` playIconSvg: {
<div class="inline"> type: String,
<div class="dropdown"> required: false,
<a class="dropdown-new btn btn-default" data-toggle="dropdown"> },
<span class="js-dropdown-play-icon-container" v-html="playIconSvg"></span> },
<i class="fa fa-caret-down"></i>
</a> template: `
<div class="inline">
<ul class="dropdown-menu dropdown-menu-align-right"> <div class="dropdown">
<li v-for="action in actions"> <a class="dropdown-new btn btn-default" data-toggle="dropdown">
<a :href="action.play_path" <span class="js-dropdown-play-icon-container" v-html="playIconSvg"></span>
data-method="post" <i class="fa fa-caret-down"></i>
rel="nofollow" </a>
class="js-manual-action-link">
<ul class="dropdown-menu dropdown-menu-align-right">
<span class="js-action-play-icon-container" v-html="playIconSvg"></span> <li v-for="action in actions">
<a :href="action.play_path"
<span> data-method="post"
{{action.name}} rel="nofollow"
</span> class="js-manual-action-link">
</a>
</li> <span class="js-action-play-icon-container" v-html="playIconSvg"></span>
</ul>
</div> <span>
{{action.name}}
</span>
</a>
</li>
</ul>
</div> </div>
`, </div>
}); `,
})(); });
/* global Vue */ /**
* Renders the external url link in environments table.
*/
const Vue = require('vue');
window.Vue = require('vue'); module.exports = Vue.component('external-url-component', {
props: {
(() => { externalUrl: {
window.gl = window.gl || {}; type: String,
window.gl.environmentsList = window.gl.environmentsList || {}; default: '',
gl.environmentsList.ExternalUrlComponent = Vue.component('external-url-component', {
props: {
externalUrl: {
type: String,
default: '',
},
}, },
},
template: ` template: `
<a class="btn external_url" :href="externalUrl" target="_blank"> <a class="btn external_url" :href="externalUrl" target="_blank">
<i class="fa fa-external-link"></i> <i class="fa fa-external-link"></i>
</a> </a>
`, `,
}); });
})();
/* global Vue */ /**
* Renders Rollback or Re deploy button in environments table depending
* of the provided property `isLastDeployment`
*/
const Vue = require('vue');
window.Vue = require('vue'); module.exports = Vue.component('rollback-component', {
props: {
(() => { retryUrl: {
window.gl = window.gl || {}; type: String,
window.gl.environmentsList = window.gl.environmentsList || {}; default: '',
},
gl.environmentsList.RollbackComponent = Vue.component('rollback-component', {
props: {
retryUrl: {
type: String,
default: '',
},
isLastDeployment: { isLastDeployment: {
type: Boolean, type: Boolean,
default: true, default: true,
},
}, },
},
template: ` template: `
<a class="btn" :href="retryUrl" data-method="post" rel="nofollow"> <a class="btn" :href="retryUrl" data-method="post" rel="nofollow">
<span v-if="isLastDeployment"> <span v-if="isLastDeployment">
Re-deploy Re-deploy
</span> </span>
<span v-else> <span v-else>
Rollback Rollback
</span> </span>
</a> </a>
`, `,
}); });
})();
/* global Vue */ /**
* Renders the stop "button" that allows stop an environment.
* Used in environments table.
*/
const Vue = require('vue');
window.Vue = require('vue'); module.exports = Vue.component('stop-component', {
props: {
(() => { stopUrl: {
window.gl = window.gl || {}; type: String,
window.gl.environmentsList = window.gl.environmentsList || {}; default: '',
gl.environmentsList.StopComponent = Vue.component('stop-component', {
props: {
stopUrl: {
type: String,
default: '',
},
}, },
},
template: ` template: `
<a class="btn stop-env-link" <a class="btn stop-env-link"
:href="stopUrl" :href="stopUrl"
data-confirm="Are you sure you want to stop this environment?" data-confirm="Are you sure you want to stop this environment?"
data-method="post" data-method="post"
rel="nofollow"> rel="nofollow">
<i class="fa fa-stop stop-env-icon"></i> <i class="fa fa-stop stop-env-icon" aria-hidden="true"></i>
</a> </a>
`, `,
}); });
})();
/* global Vue */ /**
* Renders a terminal button to open a web terminal.
* Used in environments table.
*/
const Vue = require('vue');
window.Vue = require('vue'); module.exports = Vue.component('terminal-button-component', {
props: {
(() => { terminalPath: {
window.gl = window.gl || {}; type: String,
window.gl.environmentsList = window.gl.environmentsList || {}; default: '',
},
gl.environmentsList.TerminalButtonComponent = Vue.component('terminal-button-component', { terminalIconSvg: {
props: { type: String,
terminalPath: { default: '',
type: String,
default: '',
},
terminalIconSvg: {
type: String,
default: '',
},
}, },
},
template: ` template: `
<a class="btn terminal-button" <a class="btn terminal-button"
:href="terminalPath"> :href="terminalPath">
<span class="js-terminal-icon-container" v-html="terminalIconSvg"></span> <span class="js-terminal-icon-container" v-html="terminalIconSvg"></span>
</a> </a>
`, `,
}); });
})();
/**
* Render environments table.
*/
const Vue = require('vue');
const EnvironmentItem = require('./environment_item');
module.exports = Vue.component('environment-table-component', {
components: {
'environment-item': EnvironmentItem,
},
props: {
environments: {
type: Array,
required: true,
default: () => ([]),
},
canReadEnvironment: {
type: Boolean,
required: false,
default: false,
},
canCreateDeployment: {
type: Boolean,
required: false,
default: false,
},
commitIconSvg: {
type: String,
required: false,
},
playIconSvg: {
type: String,
required: false,
},
terminalIconSvg: {
type: String,
required: false,
},
},
template: `
<table class="table ci-table environments">
<thead>
<tr>
<th class="environments-name">Environment</th>
<th class="environments-deploy">Last deployment</th>
<th class="environments-build">Job</th>
<th class="environments-commit">Commit</th>
<th class="environments-date">Updated</th>
<th class="hidden-xs environments-actions"></th>
</tr>
</thead>
<tbody>
<template v-for="model in environments"
v-bind:model="model">
<tr is="environment-item"
:model="model"
:can-create-deployment="canCreateDeployment"
:can-read-environment="canReadEnvironment"
:play-icon-svg="playIconSvg"
:terminal-icon-svg="terminalIconSvg"
:commit-icon-svg="commitIconSvg"></tr>
</template>
</tbody>
</table>
`,
});
window.Vue = require('vue'); const EnvironmentsComponent = require('./components/environment');
require('./stores/environments_store');
require('./components/environment');
require('../vue_shared/vue_resource_interceptor');
$(() => { $(() => {
window.gl = window.gl || {}; window.gl = window.gl || {};
...@@ -9,14 +6,8 @@ $(() => { ...@@ -9,14 +6,8 @@ $(() => {
if (gl.EnvironmentsListApp) { if (gl.EnvironmentsListApp) {
gl.EnvironmentsListApp.$destroy(true); gl.EnvironmentsListApp.$destroy(true);
} }
const Store = gl.environmentsList.EnvironmentsStore;
gl.EnvironmentsListApp = new gl.environmentsList.EnvironmentsComponent({ gl.EnvironmentsListApp = new EnvironmentsComponent({
el: document.querySelector('#environments-list-view'), el: document.querySelector('#environments-list-view'),
propsData: {
store: Store.create(),
},
}); });
}); });
const EnvironmentsFolderComponent = require('./environments_folder_view');
$(() => {
window.gl = window.gl || {};
if (gl.EnvironmentsListFolderApp) {
gl.EnvironmentsListFolderApp.$destroy(true);
}
gl.EnvironmentsListFolderApp = new EnvironmentsFolderComponent({
el: document.querySelector('#environments-folder-list-view'),
});
});
/* eslint-disable no-param-reassign, no-new */
/* global Flash */
const Vue = window.Vue = require('vue');
window.Vue.use(require('vue-resource'));
const EnvironmentsService = require('../services/environments_service');
const EnvironmentTable = require('../components/environments_table');
const EnvironmentsStore = require('../stores/environments_store');
require('../../vue_shared/components/table_pagination');
require('../../lib/utils/common_utils');
require('../../vue_shared/vue_resource_interceptor');
module.exports = Vue.component('environment-folder-view', {
components: {
'environment-table': EnvironmentTable,
'table-pagination': gl.VueGlPagination,
},
data() {
const environmentsData = document.querySelector('#environments-folder-list-view').dataset;
const store = new EnvironmentsStore();
const pathname = window.location.pathname;
const endpoint = `${pathname}.json`;
const folderName = pathname.substr(pathname.lastIndexOf('/') + 1);
return {
store,
folderName,
endpoint,
state: store.state,
visibility: 'available',
isLoading: false,
cssContainerClass: environmentsData.cssClass,
canCreateDeployment: environmentsData.canCreateDeployment,
canReadEnvironment: environmentsData.canReadEnvironment,
// svgs
commitIconSvg: environmentsData.commitIconSvg,
playIconSvg: environmentsData.playIconSvg,
terminalIconSvg: environmentsData.terminalIconSvg,
// Pagination Properties,
paginationInformation: {},
pageNumber: 1,
};
},
computed: {
scope() {
return gl.utils.getParameterByName('scope');
},
canReadEnvironmentParsed() {
return gl.utils.convertPermissionToBoolean(this.canReadEnvironment);
},
canCreateDeploymentParsed() {
return gl.utils.convertPermissionToBoolean(this.canCreateDeployment);
},
/**
* URL to link in the stopped tab.
*
* @return {String}
*/
stoppedPath() {
return `${window.location.pathname}?scope=stopped`;
},
/**
* URL to link in the available tab.
*
* @return {String}
*/
availablePath() {
return window.location.pathname;
},
},
/**
* Fetches all the environments and stores them.
* Toggles loading property.
*/
created() {
const scope = gl.utils.getParameterByName('scope') || this.visibility;
const pageNumber = gl.utils.getParameterByName('page') || this.pageNumber;
const endpoint = `${this.endpoint}?scope=${scope}&page=${pageNumber}`;
const service = new EnvironmentsService(endpoint);
this.isLoading = true;
return service.all()
.then(resp => ({
headers: resp.headers,
body: resp.json(),
}))
.then((response) => {
this.store.storeAvailableCount(response.body.available_count);
this.store.storeStoppedCount(response.body.stopped_count);
this.store.storeEnvironments(response.body.environments);
this.store.setPagination(response.headers);
})
.then(() => {
this.isLoading = false;
})
.catch(() => {
this.isLoading = false;
new Flash('An error occurred while fetching the environments.', 'alert');
});
},
methods: {
/**
* Will change the page number and update the URL.
*
* @param {Number} pageNumber desired page to go to.
*/
changePage(pageNumber) {
const param = gl.utils.setParamInURL('page', pageNumber);
gl.utils.visitUrl(param);
return param;
},
},
template: `
<div :class="cssContainerClass">
<div class="top-area" v-if="!isLoading">
<h4 class="js-folder-name environments-folder-name">
Environments / <b>{{folderName}}</b>
</h4>
<ul class="nav-links">
<li v-bind:class="{ 'active': scope === null || scope === 'available' }">
<a :href="availablePath" class="js-available-environments-folder-tab">
Available
<span class="badge js-available-environments-count">
{{state.availableCounter}}
</span>
</a>
</li>
<li v-bind:class="{ 'active' : scope === 'stopped' }">
<a :href="stoppedPath" class="js-stopped-environments-folder-tab">
Stopped
<span class="badge js-stopped-environments-count">
{{state.stoppedCounter}}
</span>
</a>
</li>
</ul>
</div>
<div class="environments-container">
<div class="environments-list-loading text-center" v-if="isLoading">
<i class="fa fa-spinner fa-spin"></i>
</div>
<div class="table-holder"
v-if="!isLoading && state.environments.length > 0">
<environment-table
:environments="state.environments"
:can-create-deployment="canCreateDeploymentParsed"
:can-read-environment="canReadEnvironmentParsed"
:play-icon-svg="playIconSvg"
:terminal-icon-svg="terminalIconSvg"
:commit-icon-svg="commitIconSvg">
</environment-table>
<table-pagination v-if="state.paginationInformation && state.paginationInformation.totalPages > 1"
:change="changePage"
:pageInfo="state.paginationInformation">
</table-pagination>
</div>
</div>
</div>
`,
});
/* globals Vue */ const Vue = require('vue');
/* eslint-disable no-unused-vars, no-param-reassign */
class EnvironmentsService { class EnvironmentsService {
constructor(endpoint) {
constructor(root) { this.environments = Vue.resource(endpoint);
Vue.http.options.root = root;
this.environments = Vue.resource(root);
Vue.http.interceptors.push((request, next) => {
// needed in order to not break the tests.
if ($.rails) {
request.headers['X-CSRF-Token'] = $.rails.csrfToken();
}
next();
});
} }
all() { all() {
...@@ -22,4 +10,4 @@ class EnvironmentsService { ...@@ -22,4 +10,4 @@ class EnvironmentsService {
} }
} }
window.EnvironmentsService = EnvironmentsService; module.exports = EnvironmentsService;
/* eslint-disable no-param-reassign */ require('~/lib/utils/common_utils');
(() => { /**
window.gl = window.gl || {}; * Environments Store.
window.gl.environmentsList = window.gl.environmentsList || {}; *
* Stores received environments, count of stopped environments and count of
gl.environmentsList.EnvironmentsStore = { * available environments.
state: {}, */
class EnvironmentsStore {
create() { constructor() {
this.state.environments = []; this.state = {};
this.state.stoppedCounter = 0; this.state.environments = [];
this.state.availableCounter = 0; this.state.stoppedCounter = 0;
this.state.visibility = 'available'; this.state.availableCounter = 0;
this.state.filteredEnvironments = []; this.state.paginationInformation = {};
return this; return this;
}, }
/** /**
* In order to display a tree view we need to modify the received *
* data in to a tree structure based on `environment_type` * Stores the received environments.
* sorted alphabetically. *
* In each children a `vue-` property will be added. This property will be * In the main environments endpoint, each environment has the following schema
* used to know if an item is a children mostly for css purposes. This is * { name: String, size: Number, latest: Object }
* needed because the children row is a fragment instance and therfore does * In the endpoint to retrieve environments from each folder, the environment does
* not accept non-prop attributes. * not have the `latest` key and the data is all in the root level.
* * To avoid doing this check in the view, we store both cases the same by extracting
* * what is inside the `latest` key.
* @example *
* it will transform this: * If the `size` is bigger than 1, it means it should be rendered as a folder.
* [ * In those cases we add `isFolder` key in order to render it properly.
* { name: "environment", environment_type: "review" }, *
* { name: "environment_1", environment_type: null } * @param {Array} environments
* { name: "environment_2, environment_type: "review" } * @returns {Array}
* ] */
* into this: storeEnvironments(environments = []) {
* [ const filteredEnvironments = environments.map((env) => {
* { name: "review", children: let filtered = {};
* [
* { name: "environment", environment_type: "review", vue-isChildren: true}, if (env.size > 1) {
* { name: "environment_2", environment_type: "review", vue-isChildren: true} filtered = Object.assign({}, env, { isFolder: true, folderName: env.name });
* ] }
* },
* {name: "environment_1", environment_type: null} if (env.latest) {
* ] filtered = Object.assign(filtered, env, env.latest);
* delete filtered.latest;
* } else {
* @param {Array} environments List of environments. filtered = Object.assign(filtered, env);
* @returns {Array} Tree structured array with the received environments. }
*/
storeEnvironments(environments = []) { return filtered;
this.state.stoppedCounter = this.countByState(environments, 'stopped'); });
this.state.availableCounter = this.countByState(environments, 'available');
this.state.environments = filteredEnvironments;
const environmentsTree = environments.reduce((acc, environment) => {
if (environment.environment_type !== null) { return filteredEnvironments;
const occurs = acc.filter(element => element.children && }
element.name === environment.environment_type);
setPagination(pagination = {}) {
environment['vue-isChildren'] = true; const normalizedHeaders = gl.utils.normalizeHeaders(pagination);
const paginationInformation = gl.utils.parseIntPagination(normalizedHeaders);
if (occurs.length) {
acc[acc.indexOf(occurs[0])].children.push(environment); this.state.paginationInformation = paginationInformation;
acc[acc.indexOf(occurs[0])].children.slice().sort(this.sortByName); return paginationInformation;
} else { }
acc.push({
name: environment.environment_type, /**
children: [environment], * Stores the number of available environments.
isOpen: false, *
'vue-isChildren': environment['vue-isChildren'], * @param {Number} count = 0
}); * @return {Number}
} */
} else { storeAvailableCount(count = 0) {
acc.push(environment); this.state.availableCounter = count;
} return count;
}
return acc;
}, []).slice().sort(this.sortByName); /**
* Stores the number of closed environments.
this.state.environments = environmentsTree; *
* @param {Number} count = 0
this.filterEnvironmentsByVisibility(this.state.environments); * @return {Number}
*/
return environmentsTree; storeStoppedCount(count = 0) {
}, this.state.stoppedCounter = count;
return count;
storeVisibility(visibility) { }
this.state.visibility = visibility; }
},
/** module.exports = EnvironmentsStore;
* Given the visibility prop provided by the url query parameter and which
* changes according to the active tab we need to filter which environments
* should be visible.
*
* The environments array is a recursive tree structure and we need to filter
* both root level environments and children environments.
*
* In order to acomplish that, both `filterState` and `filterEnvironmentsByVisibility`
* functions work together.
* The first one works as the filter that verifies if the given environment matches
* the given state.
* The second guarantees both root level and children elements are filtered as well.
*
* Given array of environments will return only
* the environments that match the state stored.
*
* @param {Array} array
* @return {Array}
*/
filterEnvironmentsByVisibility(arr) {
const filteredEnvironments = arr.map((item) => {
if (item.children) {
const filteredChildren = this.filterEnvironmentsByVisibility(
item.children,
).filter(Boolean);
if (filteredChildren.length) {
item.children = filteredChildren;
return item;
}
}
return this.filterState(this.state.visibility, item);
}).filter(Boolean);
this.state.filteredEnvironments = filteredEnvironments;
return filteredEnvironments;
},
/**
* Given the state and the environment,
* returns only if the environment state matches the one provided.
*
* @param {String} state
* @param {Object} environment
* @return {Object}
*/
filterState(state, environment) {
return environment.state === state && environment;
},
/**
* Toggles folder open property given the environment type.
*
* @param {String} envType
* @return {Array}
*/
toggleFolder(envType) {
const environments = this.state.environments;
const environmentsCopy = environments.map((env) => {
if (env['vue-isChildren'] && env.name === envType) {
env.isOpen = !env.isOpen;
}
return env;
});
this.state.environments = environmentsCopy;
return environmentsCopy;
},
/**
* Given an array of environments, returns the number of environments
* that have the given state.
*
* @param {Array} environments
* @param {String} state
* @returns {Number}
*/
countByState(environments, state) {
return environments.filter(env => env.state === state).length;
},
/**
* Sorts the two objects provided by their name.
*
* @param {Object} a
* @param {Object} b
* @returns {Number}
*/
sortByName(a, b) {
const nameA = a.name.toUpperCase();
const nameB = b.name.toUpperCase();
return nameA < nameB ? -1 : nameA > nameB ? 1 : 0; // eslint-disable-line
},
};
})();
...@@ -13,4 +13,4 @@ ...@@ -13,4 +13,4 @@
return $(this).removeAttr('disabled').removeClass('disabled'); return $(this).removeAttr('disabled').removeClass('disabled');
} }
}); });
}).call(this); }).call(window);
...@@ -144,4 +144,4 @@ ...@@ -144,4 +144,4 @@
} }
}); });
}; };
}).call(this); }).call(window);
...@@ -39,4 +39,4 @@ ...@@ -39,4 +39,4 @@
return Flash; return Flash;
})(); })();
}).call(this); }).call(window);
...@@ -83,12 +83,12 @@ ...@@ -83,12 +83,12 @@
_a = decodeURI("%C3%80"); _a = decodeURI("%C3%80");
_y = decodeURI("%C3%BF"); _y = decodeURI("%C3%BF");
regexp = new RegExp("^(?:\\B|[^a-zA-Z0-9_" + atSymbolsWithoutBar + "]|\\s)" + flag + "(?![" + atSymbolsWithBar + "])(([A-Za-z" + _a + "-" + _y + "0-9_\'\.\+\-]|[^\\x00-\\x7a])*)$", 'gi'); regexp = new RegExp("^(?:\\B|[^a-zA-Z0-9_" + atSymbolsWithoutBar + "]|\\s)" + flag + "(?!" + atSymbolsWithBar + ")((?:[A-Za-z" + _a + "-" + _y + "0-9_\'\.\+\-]|[^\\x00-\\x7a])*)$", 'gi');
match = regexp.exec(subtext); match = regexp.exec(subtext);
if (match) { if (match) {
return (match[1] || match[1] === "") ? match[1] : match[2]; return match[1];
} else { } else {
return null; return null;
} }
...@@ -103,6 +103,9 @@ ...@@ -103,6 +103,9 @@
this.input.each((i, input) => { this.input.each((i, input) => {
const $input = $(input); const $input = $(input);
$input.off('focus.setupAtWho').on('focus.setupAtWho', this.setupAtWho.bind(this, $input)); $input.off('focus.setupAtWho').on('focus.setupAtWho', this.setupAtWho.bind(this, $input));
// This triggers at.js again
// Needed for slash commands with suffixes (ex: /label ~)
$input.on('inserted-commands.atwho', $input.trigger.bind($input, 'keyup'));
}); });
}, },
setupAtWho: function($input) { setupAtWho: function($input) {
...@@ -377,4 +380,4 @@ ...@@ -377,4 +380,4 @@
(dataToInspect === loadingState || dataToInspect.name === loadingState); (dataToInspect === loadingState || dataToInspect.name === loadingState);
} }
}; };
}).call(this); }).call(window);
...@@ -846,4 +846,4 @@ ...@@ -846,4 +846,4 @@
} }
}); });
}; };
}).call(this); }).call(window);
...@@ -15,4 +15,4 @@ ...@@ -15,4 +15,4 @@
return StatGraph; return StatGraph;
})(); })();
}).call(this); }).call(window);
...@@ -113,4 +113,4 @@ window.d3 = require('d3'); ...@@ -113,4 +113,4 @@ window.d3 = require('d3');
return ContributorsStatGraph; return ContributorsStatGraph;
})(); })();
}).call(this); }).call(window);
...@@ -273,4 +273,4 @@ window.d3 = require('d3'); ...@@ -273,4 +273,4 @@ window.d3 = require('d3');
return ContributorsAuthorGraph; return ContributorsAuthorGraph;
})(ContributorsGraph); })(ContributorsGraph);
}).call(this); }).call(window);
...@@ -135,4 +135,4 @@ ...@@ -135,4 +135,4 @@
} }
} }
}; };
}).call(this); }).call(window);
...@@ -17,4 +17,4 @@ ...@@ -17,4 +17,4 @@
return GroupAvatar; return GroupAvatar;
})(); })();
}).call(this); }).call(window);
...@@ -68,4 +68,4 @@ ...@@ -68,4 +68,4 @@
return GroupsSelect; return GroupsSelect;
})(); })();
}).call(this); }).call(window);
...@@ -78,4 +78,4 @@ ...@@ -78,4 +78,4 @@
new window.ImporterStatus(jobsImportPath, importPath); new window.ImporterStatus(jobsImportPath, importPath);
} }
}); });
}).call(this); }).call(window);
...@@ -76,4 +76,4 @@ ...@@ -76,4 +76,4 @@
return IssuableContext; return IssuableContext;
})(); })();
}).call(this); }).call(window);
...@@ -156,4 +156,4 @@ ...@@ -156,4 +156,4 @@
return IssuableForm; return IssuableForm;
})(); })();
}).call(this); }).call(window);
...@@ -3,7 +3,7 @@ ...@@ -3,7 +3,7 @@
require('./flash'); require('./flash');
require('vendor/jquery.waitforimages'); require('vendor/jquery.waitforimages');
require('vendor/task_list'); require('./task_list');
(function() { (function() {
var bind = function(fn, me) { return function() { return fn.apply(me, arguments); }; }; var bind = function(fn, me) { return function() { return fn.apply(me, arguments); }; };
...@@ -11,10 +11,16 @@ require('vendor/task_list'); ...@@ -11,10 +11,16 @@ require('vendor/task_list');
this.Issue = (function() { this.Issue = (function() {
function Issue() { function Issue() {
this.submitNoteForm = bind(this.submitNoteForm, this); this.submitNoteForm = bind(this.submitNoteForm, this);
// Prevent duplicate event bindings
this.disableTaskList();
if ($('a.btn-close').length) { if ($('a.btn-close').length) {
this.initTaskList(); this.taskList = new gl.TaskList({
dataType: 'issue',
fieldName: 'description',
selector: '.detail-page-description',
onSuccess: (result) => {
document.querySelector('#task_status').innerText = result.task_status;
document.querySelector('#task_status_short').innerText = result.task_status_short;
}
});
this.initIssueBtnEventListeners(); this.initIssueBtnEventListeners();
} }
this.initMergeRequests(); this.initMergeRequests();
...@@ -22,11 +28,6 @@ require('vendor/task_list'); ...@@ -22,11 +28,6 @@ require('vendor/task_list');
this.initCanCreateBranch(); this.initCanCreateBranch();
} }
Issue.prototype.initTaskList = function() {
$('.detail-page-description .js-task-list-container').taskList('enable');
return $(document).on('tasklist:changed', '.detail-page-description .js-task-list-container', this.updateTaskList);
};
Issue.prototype.initIssueBtnEventListeners = function() { Issue.prototype.initIssueBtnEventListeners = function() {
var _this, issueFailMessage; var _this, issueFailMessage;
_this = this; _this = this;
...@@ -54,16 +55,19 @@ require('vendor/task_list'); ...@@ -54,16 +55,19 @@ require('vendor/task_list');
success: function(data, textStatus, jqXHR) { success: function(data, textStatus, jqXHR) {
if ('id' in data) { if ('id' in data) {
$(document).trigger('issuable:change'); $(document).trigger('issuable:change');
const currentTotal = Number($('.issue_counter').text());
if (isClose) { if (isClose) {
$('a.btn-close').addClass('hidden'); $('a.btn-close').addClass('hidden');
$('a.btn-reopen').removeClass('hidden'); $('a.btn-reopen').removeClass('hidden');
$('div.status-box-closed').removeClass('hidden'); $('div.status-box-closed').removeClass('hidden');
$('div.status-box-open').addClass('hidden'); $('div.status-box-open').addClass('hidden');
$('.issue_counter').text(currentTotal - 1);
} else { } else {
$('a.btn-reopen').addClass('hidden'); $('a.btn-reopen').addClass('hidden');
$('a.btn-close').removeClass('hidden'); $('a.btn-close').removeClass('hidden');
$('div.status-box-closed').addClass('hidden'); $('div.status-box-closed').addClass('hidden');
$('div.status-box-open').removeClass('hidden'); $('div.status-box-open').removeClass('hidden');
$('.issue_counter').text(currentTotal + 1);
} }
} else { } else {
new Flash(issueFailMessage, 'alert'); new Flash(issueFailMessage, 'alert');
...@@ -82,30 +86,6 @@ require('vendor/task_list'); ...@@ -82,30 +86,6 @@ require('vendor/task_list');
} }
}; };
Issue.prototype.disableTaskList = function() {
$('.detail-page-description .js-task-list-container').taskList('disable');
return $(document).off('tasklist:changed', '.detail-page-description .js-task-list-container');
};
Issue.prototype.updateTaskList = function() {
var patchData;
patchData = {};
patchData['issue'] = {
'description': $('.js-task-list-field', this).val()
};
return $.ajax({
type: 'PATCH',
url: $('form.js-issuable-update').attr('action'),
data: patchData,
success: function(issue) {
document.querySelector('#task_status').innerText = issue.task_status;
document.querySelector('#task_status_short').innerText = issue.task_status_short;
}
});
// TODO (rspeicher): Make the issue description inline-editable like a note so
// that we can re-use its form here
};
Issue.prototype.initMergeRequests = function() { Issue.prototype.initMergeRequests = function() {
var $container; var $container;
$container = $('#merge-requests'); $container = $('#merge-requests');
...@@ -152,4 +132,4 @@ require('vendor/task_list'); ...@@ -152,4 +132,4 @@ require('vendor/task_list');
return Issue; return Issue;
})(); })();
}).call(this); }).call(window);
...@@ -31,4 +31,4 @@ ...@@ -31,4 +31,4 @@
return IssueStatusSelect; return IssueStatusSelect;
})(); })();
}).call(this); }).call(window);
...@@ -43,4 +43,4 @@ ...@@ -43,4 +43,4 @@
return Labels; return Labels;
})(); })();
}).call(this); }).call(window);
...@@ -504,4 +504,4 @@ ...@@ -504,4 +504,4 @@
return LabelsSelect; return LabelsSelect;
})(); })();
}).call(this); }).call(window);
...@@ -44,4 +44,4 @@ ...@@ -44,4 +44,4 @@
} }
}); });
}); });
}).call(this); }).call(window);
...@@ -4,4 +4,4 @@ ...@@ -4,4 +4,4 @@
(function() { (function() {
}).call(this); }).call(window);
...@@ -6,4 +6,4 @@ ...@@ -6,4 +6,4 @@
(function() { (function() {
}).call(this); }).call(window);
...@@ -46,4 +46,4 @@ ...@@ -46,4 +46,4 @@
return dfd.promise(); return dfd.promise();
}; };
})(window); })(window);
}).call(this); }).call(window);
...@@ -231,6 +231,21 @@ ...@@ -231,6 +231,21 @@
return upperCaseHeaders; return upperCaseHeaders;
}; };
/**
* Parses pagination object string values into numbers.
*
* @param {Object} paginationInformation
* @returns {Object}
*/
w.gl.utils.parseIntPagination = paginationInformation => ({
perPage: parseInt(paginationInformation['X-PER-PAGE'], 10),
page: parseInt(paginationInformation['X-PAGE'], 10),
total: parseInt(paginationInformation['X-TOTAL'], 10),
totalPages: parseInt(paginationInformation['X-TOTAL-PAGES'], 10),
nextPage: parseInt(paginationInformation['X-NEXT-PAGE'], 10),
previousPage: parseInt(paginationInformation['X-PREV-PAGE'], 10),
});
/** /**
* Transforms a DOMStringMap into a plain object. * Transforms a DOMStringMap into a plain object.
* *
...@@ -241,5 +256,45 @@ ...@@ -241,5 +256,45 @@
acc[element] = DOMStringMapObject[element]; acc[element] = DOMStringMapObject[element];
return acc; return acc;
}, {}); }, {});
/**
* Updates the search parameter of a URL given the parameter and values provided.
*
* If no search params are present we'll add it.
* If param for page is already present, we'll update it
* If there are params but not for the given one, we'll add it at the end.
* Returns the new search parameters.
*
* @param {String} param
* @param {Number|String|Undefined|Null} value
* @return {String}
*/
w.gl.utils.setParamInURL = (param, value) => {
let search;
const locationSearch = window.location.search;
if (locationSearch.length === 0) {
search = `?${param}=${value}`;
}
if (locationSearch.indexOf(param) !== -1) {
const regex = new RegExp(param + '=\\d');
search = locationSearch.replace(regex, `${param}=${value}`);
}
if (locationSearch.length && locationSearch.indexOf(param) === -1) {
search = `${locationSearch}&${param}=${value}`;
}
return search;
};
/**
* Converts permission provided as strings to booleans.
*
* @param {String} string
* @returns {Boolean}
*/
w.gl.utils.convertPermissionToBoolean = permission => permission === 'true';
})(window); })(window);
}).call(this); }).call(window);
...@@ -123,4 +123,4 @@ window.dateFormat = require('vendor/date.format'); ...@@ -123,4 +123,4 @@ window.dateFormat = require('vendor/date.format');
return Math.floor((date2 - date1) / millisecondsPerDay); return Math.floor((date2 - date1) / millisecondsPerDay);
}; };
})(window); })(window);
}).call(this); }).call(window);
...@@ -44,4 +44,4 @@ ...@@ -44,4 +44,4 @@
w.notify = notifyMe; w.notify = notifyMe;
return w.notifyPermissions = notifyPermissions; return w.notifyPermissions = notifyPermissions;
})(window); })(window);
}).call(this); }).call(window);
/* eslint-disable func-names, space-before-function-paren, wrap-iife, no-var, no-param-reassign, no-cond-assign, quotes, one-var, one-var-declaration-per-line, operator-assignment, no-else-return, prefer-template, prefer-arrow-callback, no-empty, max-len, consistent-return, no-unused-vars, no-return-assign, max-len */ /* eslint-disable func-names, space-before-function-paren, wrap-iife, no-var, no-param-reassign, no-cond-assign, quotes, one-var, one-var-declaration-per-line, operator-assignment, no-else-return, prefer-template, prefer-arrow-callback, no-empty, max-len, consistent-return, no-unused-vars, no-return-assign, max-len */
require('vendor/latinise');
(function() { (function() {
(function(w) { (function(w) {
var base; var base;
...@@ -164,8 +166,14 @@ ...@@ -164,8 +166,14 @@
gl.text.pluralize = function(str, count) { gl.text.pluralize = function(str, count) {
return str + (count > 1 || count === 0 ? 's' : ''); return str + (count > 1 || count === 0 ? 's' : '');
}; };
return gl.text.truncate = function(string, maxLength) { gl.text.truncate = function(string, maxLength) {
return string.substr(0, (maxLength - 3)) + '...'; return string.substr(0, (maxLength - 3)) + '...';
}; };
gl.text.dasherize = function(str) {
return str.replace(/[_\s]+/g, '-');
};
gl.text.slugify = function(str) {
return str.trim().toLowerCase().latinise();
};
})(window); })(window);
}).call(this); }).call(window);
...@@ -12,4 +12,4 @@ ...@@ -12,4 +12,4 @@
return (obj != null) && (obj.constructor === Object); return (obj != null) && (obj.constructor === Object);
}; };
})(window); })(window);
}).call(this); }).call(window);
...@@ -83,4 +83,4 @@ ...@@ -83,4 +83,4 @@
document.location.href = url; document.location.href = url;
}; };
})(window); })(window);
}).call(this); }).call(window);
...@@ -179,4 +179,4 @@ require('vendor/jquery.scrollTo'); ...@@ -179,4 +179,4 @@ require('vendor/jquery.scrollTo');
return LineHighlighter; return LineHighlighter;
})(); })();
}).call(this); }).call(window);
...@@ -4,4 +4,4 @@ ...@@ -4,4 +4,4 @@
window.addEventListener('beforeunload', function() { window.addEventListener('beforeunload', function() {
$('.tanuki-logo').addClass('animate'); $('.tanuki-logo').addClass('animate');
}); });
}).call(this); }).call(window);
...@@ -49,4 +49,4 @@ ...@@ -49,4 +49,4 @@
inputs.each(toggleClearInput); inputs.each(toggleClearInput);
}; };
}).call(this); }).call(window);
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
/* global MergeRequestTabs */ /* global MergeRequestTabs */
require('vendor/jquery.waitforimages'); require('vendor/jquery.waitforimages');
require('vendor/task_list'); require('./task_list');
require('./merge_request_tabs'); require('./merge_request_tabs');
(function() { (function() {
...@@ -24,12 +24,18 @@ require('./merge_request_tabs'); ...@@ -24,12 +24,18 @@ require('./merge_request_tabs');
}; };
})(this)); })(this));
this.initTabs(); this.initTabs();
// Prevent duplicate event bindings
this.disableTaskList();
this.initMRBtnListeners(); this.initMRBtnListeners();
this.initCommitMessageListeners(); this.initCommitMessageListeners();
if ($("a.btn-close").length) { if ($("a.btn-close").length) {
this.initTaskList(); this.taskList = new gl.TaskList({
dataType: 'merge_request',
fieldName: 'description',
selector: '.detail-page-description',
onSuccess: (result) => {
document.querySelector('#task_status').innerText = result.task_status;
document.querySelector('#task_status_short').innerText = result.task_status_short;
}
});
} }
} }
...@@ -50,11 +56,6 @@ require('./merge_request_tabs'); ...@@ -50,11 +56,6 @@ require('./merge_request_tabs');
return this.$('.all-commits').removeClass('hide'); return this.$('.all-commits').removeClass('hide');
}; };
MergeRequest.prototype.initTaskList = function() {
$('.detail-page-description .js-task-list-container').taskList('enable');
return $(document).on('tasklist:changed', '.detail-page-description .js-task-list-container', this.updateTaskList);
};
MergeRequest.prototype.initMRBtnListeners = function() { MergeRequest.prototype.initMRBtnListeners = function() {
var _this; var _this;
_this = this; _this = this;
...@@ -85,30 +86,6 @@ require('./merge_request_tabs'); ...@@ -85,30 +86,6 @@ require('./merge_request_tabs');
} }
}; };
MergeRequest.prototype.disableTaskList = function() {
$('.detail-page-description .js-task-list-container').taskList('disable');
return $(document).off('tasklist:changed', '.detail-page-description .js-task-list-container');
};
MergeRequest.prototype.updateTaskList = function() {
var patchData;
patchData = {};
patchData['merge_request'] = {
'description': $('.js-task-list-field', this).val()
};
return $.ajax({
type: 'PATCH',
url: $('form.js-issuable-update').attr('action'),
data: patchData,
success: function(mergeRequest) {
document.querySelector('#task_status').innerText = mergeRequest.task_status;
document.querySelector('#task_status_short').innerText = mergeRequest.task_status_short;
}
});
// TODO (rspeicher): Make the merge request description inline-editable like a
// note so that we can re-use its form here
};
MergeRequest.prototype.initCommitMessageListeners = function() { MergeRequest.prototype.initCommitMessageListeners = function() {
$(document).on('click', 'a.js-with-description-link', function(e) { $(document).on('click', 'a.js-with-description-link', function(e) {
var textarea = $('textarea.js-commit-message'); var textarea = $('textarea.js-commit-message');
...@@ -131,4 +108,4 @@ require('./merge_request_tabs'); ...@@ -131,4 +108,4 @@ require('./merge_request_tabs');
return MergeRequest; return MergeRequest;
})(); })();
}).call(this); }).call(window);
...@@ -252,7 +252,6 @@ require('./smart_interval'); ...@@ -252,7 +252,6 @@ require('./smart_interval');
$('.ci_widget.ci-error').show(); $('.ci_widget.ci-error').show();
this.setMergeButtonClass('btn-danger'); this.setMergeButtonClass('btn-danger');
} }
this.initMiniPipelineGraph();
}; };
MergeRequestWidget.prototype.showCICoverage = function(coverage) { MergeRequestWidget.prototype.showCICoverage = function(coverage) {
......
...@@ -42,4 +42,4 @@ ...@@ -42,4 +42,4 @@
return MergedButtons; return MergedButtons;
})(); })();
}).call(this); }).call(window);
...@@ -172,4 +172,4 @@ ...@@ -172,4 +172,4 @@
return Milestone; return Milestone;
})(); })();
}).call(this); }).call(window);
...@@ -199,4 +199,4 @@ ...@@ -199,4 +199,4 @@
return MilestoneSelect; return MilestoneSelect;
})(); })();
}).call(this); }).call(window);
...@@ -28,7 +28,7 @@ ...@@ -28,7 +28,7 @@
* All dropdown events are fired at the .dropdown-menu's parent element. * All dropdown events are fired at the .dropdown-menu's parent element.
*/ */
bindEvents() { bindEvents() {
$(document).on('shown.bs.dropdown', this.container, this.getBuildsList); $(document).off('shown.bs.dropdown', this.container).on('shown.bs.dropdown', this.container, this.getBuildsList);
} }
/** /**
......
...@@ -83,4 +83,4 @@ ...@@ -83,4 +83,4 @@
return NamespaceSelects; return NamespaceSelects;
})(); })();
}).call(this); }).call(window);
...@@ -421,4 +421,4 @@ ...@@ -421,4 +421,4 @@
y: h y: h
}); });
}; };
}).call(this); }).call(window);
...@@ -17,4 +17,4 @@ ...@@ -17,4 +17,4 @@
return Network; return Network;
})(); })();
}).call(this); }).call(window);
...@@ -19,4 +19,4 @@ requireAll(require.context('.', false, /^\.\/(?!network_bundle).*\.(js|es6)$/)); ...@@ -19,4 +19,4 @@ requireAll(require.context('.', false, /^\.\/(?!network_bundle).*\.(js|es6)$/));
}); });
return new ShortcutsNetwork(network_graph.branch_graph); return new ShortcutsNetwork(network_graph.branch_graph);
}); });
}).call(this); }).call(window);
...@@ -100,4 +100,4 @@ ...@@ -100,4 +100,4 @@
return NewBranchForm; return NewBranchForm;
})(); })();
}).call(this); }).call(window);
...@@ -30,4 +30,4 @@ ...@@ -30,4 +30,4 @@
return NewCommitForm; return NewCommitForm;
})(); })();
}).call(this); }).call(window);
...@@ -11,7 +11,7 @@ require('./dropzone_input'); ...@@ -11,7 +11,7 @@ require('./dropzone_input');
require('./gfm_auto_complete'); require('./gfm_auto_complete');
require('vendor/jquery.caret'); // required by jquery.atwho require('vendor/jquery.caret'); // required by jquery.atwho
require('vendor/jquery.atwho'); require('vendor/jquery.atwho');
require('vendor/task_list'); require('./task_list');
(function() { (function() {
var bind = function(fn, me) { return function() { return fn.apply(me, arguments); }; }; var bind = function(fn, me) { return function() { return fn.apply(me, arguments); }; };
...@@ -51,7 +51,11 @@ require('vendor/task_list'); ...@@ -51,7 +51,11 @@ require('vendor/task_list');
this.addBinding(); this.addBinding();
this.setPollingInterval(); this.setPollingInterval();
this.setupMainTargetNoteForm(); this.setupMainTargetNoteForm();
this.initTaskList(); this.taskList = new gl.TaskList({
dataType: 'note',
fieldName: 'note',
selector: '.notes'
});
this.collapseLongCommitList(); this.collapseLongCommitList();
// We are in the Merge Requests page so we need another edit form for Changes tab // We are in the Merge Requests page so we need another edit form for Changes tab
...@@ -125,8 +129,6 @@ require('vendor/task_list'); ...@@ -125,8 +129,6 @@ require('vendor/task_list');
$(document).off("keydown", ".js-note-text"); $(document).off("keydown", ".js-note-text");
$(document).off('click', '.js-comment-resolve-button'); $(document).off('click', '.js-comment-resolve-button');
$(document).off("click", '.system-note-commit-list-toggler'); $(document).off("click", '.system-note-commit-list-toggler');
$('.note .js-task-list-container').taskList('disable');
return $(document).off('tasklist:changed', '.note .js-task-list-container');
}; };
Notes.prototype.keydownNoteText = function(e) { Notes.prototype.keydownNoteText = function(e) {
...@@ -286,7 +288,7 @@ require('vendor/task_list'); ...@@ -286,7 +288,7 @@ require('vendor/task_list');
// Update datetime format on the recent note // Update datetime format on the recent note
gl.utils.localTimeAgo($notesList.find("#note_" + note.id + " .js-timeago"), false); gl.utils.localTimeAgo($notesList.find("#note_" + note.id + " .js-timeago"), false);
this.collapseLongCommitList(); this.collapseLongCommitList();
this.initTaskList(); this.taskList.init();
this.refresh(); this.refresh();
return this.updateNotesCount(1); return this.updateNotesCount(1);
} }
...@@ -863,15 +865,6 @@ require('vendor/task_list'); ...@@ -863,15 +865,6 @@ require('vendor/task_list');
} }
}; };
Notes.prototype.initTaskList = function() {
this.enableTaskList();
return $(document).on('tasklist:changed', '.note .js-task-list-container', this.updateTaskList.bind(this));
};
Notes.prototype.enableTaskList = function() {
return $('.note .js-task-list-container').taskList('enable');
};
Notes.prototype.putEditFormInPlace = function($el) { Notes.prototype.putEditFormInPlace = function($el) {
var $editForm = $(this.getEditFormSelector($el)); var $editForm = $(this.getEditFormSelector($el));
var $note = $el.closest('.note'); var $note = $el.closest('.note');
...@@ -896,17 +889,6 @@ require('vendor/task_list'); ...@@ -896,17 +889,6 @@ require('vendor/task_list');
$editForm.find('.referenced-users').hide(); $editForm.find('.referenced-users').hide();
}; };
Notes.prototype.updateTaskList = function(e) {
var $target = $(e.target);
var $list = $target.closest('.js-task-list-container');
var $editForm = $(this.getEditFormSelector($target));
var $note = $list.closest('.note');
this.putEditFormInPlace($list);
$editForm.find('#note_note').val($note.find('.original-task-list').val());
$('form', $list).submit();
};
Notes.prototype.updateNotesCount = function(updateCount) { Notes.prototype.updateNotesCount = function(updateCount) {
return this.notesCountBadge.text(parseInt(this.notesCountBadge.text(), 10) + updateCount); return this.notesCountBadge.text(parseInt(this.notesCountBadge.text(), 10) + updateCount);
}; };
...@@ -955,4 +937,4 @@ require('vendor/task_list'); ...@@ -955,4 +937,4 @@ require('vendor/task_list');
return Notes; return Notes;
})(); })();
}).call(this); }).call(window);
...@@ -28,4 +28,4 @@ ...@@ -28,4 +28,4 @@
return NotificationsDropdown; return NotificationsDropdown;
})(); })();
}).call(this); }).call(window);
...@@ -54,4 +54,4 @@ ...@@ -54,4 +54,4 @@
return NotificationsForm; return NotificationsForm;
})(); })();
}).call(this); }).call(window);
...@@ -126,4 +126,4 @@ ...@@ -126,4 +126,4 @@
return Project; return Project;
})(); })();
}).call(this); }).call(window);
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment