Commit 70eb0c6a authored by Lin Jen-Shin's avatar Lin Jen-Shin

Merge remote-tracking branch 'upstream/master' into rename-builds-controller

* upstream/master: (307 commits)
  Address feedback
  Add small update for the i18n guide.
  update webpack to v2.6.1 patch release to fix "Can't find variable: Promise" error
  update webpack-bundle-analyzer past v2.4.1 to support NamedChunksPlugin
  name all webpack chunks to improve long term cacheability
  add NameAllModulesPlugin to cover shortcomings of NamedModulesPlugin
  upgrade to latest webpack version
  Only use DROP INDEX CONCURRENTLY on postgreql 9.2+
  Provide default for calculating label text color (!11681)
  Add failing test for #32728
  Bugfix: Always use the default language when generating emails.
  Remove unecessary commit pattern check
  Add regexp_for_value helper method
  Remove shared example and improve sub_group_issuables_spec.rb
  Remove 'should' from scenario in has_subgroup_title_spec.rb
  Cartfile git and binary methods cannot take a GitHub repo
  Fix terminals support for Kubernetes service
  Add review comments to compare_spec.rb
  Fix transient error clicking dropdown items in compare_spec.rb
  Use non-global jQuery reference within raven bundle
  ...
parents ec525efd 7a509c26

Too many changes to show.

To preserve performance only 1000 of 1000+ files are displayed.

......@@ -27,6 +27,7 @@
},
"rules": {
"filenames/match-regex": [2, "^[a-z0-9_]+$"],
"import/no-commonjs": "error",
"no-multiple-empty-lines": ["error", { "max": 1 }],
"promise/catch-or-return": "error"
}
......
This diff is collapsed.
......@@ -969,6 +969,12 @@ RSpec/DescribeSymbol:
RSpec/DescribedClass:
Enabled: true
# Checks if an example group does not include any tests.
RSpec/EmptyExampleGroup:
Enabled: true
CustomIncludeMethods:
- run_permission_checks
# Checks for long example.
RSpec/ExampleLength:
Enabled: false
......@@ -987,6 +993,10 @@ RSpec/ExampleWording:
RSpec/ExpectActual:
Enabled: true
# Checks for opportunities to use `expect { … }.to output`.
RSpec/ExpectOutput:
Enabled: true
# Checks the file and folder naming of the spec file.
RSpec/FilePath:
Enabled: true
......
......@@ -10,11 +10,6 @@
RSpec/BeforeAfterAll:
Enabled: false
# Offense count: 15
# Configuration parameters: CustomIncludeMethods.
RSpec/EmptyExampleGroup:
Enabled: false
# Offense count: 233
RSpec/EmptyLineAfterFinalLet:
Enabled: false
......@@ -23,10 +18,6 @@ RSpec/EmptyLineAfterFinalLet:
RSpec/EmptyLineAfterSubject:
Enabled: false
# Offense count: 3
RSpec/ExpectOutput:
Enabled: false
# Offense count: 72
# Configuration parameters: EnforcedStyle, SupportedStyles.
# SupportedStyles: implicit, each, example
......
This diff is collapsed.
......@@ -341,7 +341,7 @@ GEM
grape-entity (0.6.0)
activesupport
multi_json (>= 1.3.2)
grpc (1.2.5)
grpc (1.3.4)
google-protobuf (~> 3.1)
googleauth (~> 0.5.1)
haml (4.0.7)
......
9.2.0-pre
9.3.0-pre
/* eslint-disable func-names, space-before-function-paren, quotes, object-shorthand, camelcase, no-var, comma-dangle, prefer-arrow-callback, quote-props, no-param-reassign, max-len */
var Api = {
groupsPath: "/api/:version/groups.json",
groupPath: "/api/:version/groups/:id.json",
namespacesPath: "/api/:version/namespaces.json",
groupProjectsPath: "/api/:version/groups/:id/projects.json",
projectsPath: "/api/:version/projects.json?simple=true",
labelsPath: "/:namespace_path/:project_path/labels",
licensePath: "/api/:version/templates/licenses/:key",
gitignorePath: "/api/:version/templates/gitignores/:key",
gitlabCiYmlPath: "/api/:version/templates/gitlab_ci_ymls/:key",
dockerfilePath: "/api/:version/templates/dockerfiles/:key",
issuableTemplatePath: "/:namespace_path/:project_path/templates/:type/:key",
group: function(group_id, callback) {
var url = Api.buildUrl(Api.groupPath)
.replace(':id', group_id);
import $ from 'jquery';
const Api = {
groupsPath: '/api/:version/groups.json',
groupPath: '/api/:version/groups/:id.json',
namespacesPath: '/api/:version/namespaces.json',
groupProjectsPath: '/api/:version/groups/:id/projects.json',
projectsPath: '/api/:version/projects.json?simple=true',
labelsPath: '/:namespace_path/:project_path/labels',
licensePath: '/api/:version/templates/licenses/:key',
gitignorePath: '/api/:version/templates/gitignores/:key',
gitlabCiYmlPath: '/api/:version/templates/gitlab_ci_ymls/:key',
dockerfilePath: '/api/:version/templates/dockerfiles/:key',
issuableTemplatePath: '/:namespace_path/:project_path/templates/:type/:key',
usersPath: '/api/:version/users.json',
group(groupId, callback) {
const url = Api.buildUrl(Api.groupPath)
.replace(':id', groupId);
return $.ajax({
url: url,
dataType: "json"
}).done(function(group) {
return callback(group);
});
url,
dataType: 'json',
})
.done(group => callback(group));
},
// Return groups list. Filtered by query
groups: function(query, options, callback) {
var url = Api.buildUrl(Api.groupsPath);
groups(query, options, callback) {
const url = Api.buildUrl(Api.groupsPath);
return $.ajax({
url: url,
data: $.extend({
url,
data: Object.assign({
search: query,
per_page: 20
per_page: 20,
}, options),
dataType: "json"
}).done(function(groups) {
return callback(groups);
});
dataType: 'json',
})
.done(groups => callback(groups));
},
// Return namespaces list. Filtered by query
namespaces: function(query, callback) {
var url = Api.buildUrl(Api.namespacesPath);
namespaces(query, callback) {
const url = Api.buildUrl(Api.namespacesPath);
return $.ajax({
url: url,
url,
data: {
search: query,
per_page: 20
per_page: 20,
},
dataType: "json"
}).done(function(namespaces) {
return callback(namespaces);
});
dataType: 'json',
}).done(namespaces => callback(namespaces));
},
// Return projects list. Filtered by query
projects: function(query, options, callback) {
var url = Api.buildUrl(Api.projectsPath);
projects(query, options, callback) {
const url = Api.buildUrl(Api.projectsPath);
return $.ajax({
url: url,
data: $.extend({
url,
data: Object.assign({
search: query,
per_page: 20,
membership: true
membership: true,
}, options),
dataType: "json"
}).done(function(projects) {
return callback(projects);
});
dataType: 'json',
})
.done(projects => callback(projects));
},
newLabel: function(namespace_path, project_path, data, callback) {
var url = Api.buildUrl(Api.labelsPath)
.replace(':namespace_path', namespace_path)
.replace(':project_path', project_path);
newLabel(namespacePath, projectPath, data, callback) {
const url = Api.buildUrl(Api.labelsPath)
.replace(':namespace_path', namespacePath)
.replace(':project_path', projectPath);
return $.ajax({
url: url,
type: "POST",
data: { 'label': data },
dataType: "json"
}).done(function(label) {
return callback(label);
}).error(function(message) {
return callback(message.responseJSON);
});
url,
type: 'POST',
data: { label: data },
dataType: 'json',
})
.done(label => callback(label))
.error(message => callback(message.responseJSON));
},
// Return group projects list. Filtered by query
groupProjects: function(group_id, query, callback) {
var url = Api.buildUrl(Api.groupProjectsPath)
.replace(':id', group_id);
groupProjects(groupId, query, callback) {
const url = Api.buildUrl(Api.groupProjectsPath)
.replace(':id', groupId);
return $.ajax({
url: url,
url,
data: {
search: query,
per_page: 20
per_page: 20,
},
dataType: "json"
}).done(function(projects) {
return callback(projects);
});
dataType: 'json',
})
.done(projects => callback(projects));
},
// Return text for a specific license
licenseText: function(key, data, callback) {
var url = Api.buildUrl(Api.licensePath)
licenseText(key, data, callback) {
const url = Api.buildUrl(Api.licensePath)
.replace(':key', key);
return $.ajax({
url: url,
data: data
}).done(function(license) {
return callback(license);
});
url,
data,
})
.done(license => callback(license));
},
gitignoreText: function(key, callback) {
var url = Api.buildUrl(Api.gitignorePath)
gitignoreText(key, callback) {
const url = Api.buildUrl(Api.gitignorePath)
.replace(':key', key);
return $.get(url, function(gitignore) {
return callback(gitignore);
});
return $.get(url, gitignore => callback(gitignore));
},
gitlabCiYml: function(key, callback) {
var url = Api.buildUrl(Api.gitlabCiYmlPath)
gitlabCiYml(key, callback) {
const url = Api.buildUrl(Api.gitlabCiYmlPath)
.replace(':key', key);
return $.get(url, function(file) {
return callback(file);
});
return $.get(url, file => callback(file));
},
dockerfileYml: function(key, callback) {
var url = Api.buildUrl(Api.dockerfilePath).replace(':key', key);
dockerfileYml(key, callback) {
const url = Api.buildUrl(Api.dockerfilePath).replace(':key', key);
$.get(url, callback);
},
issueTemplate: function(namespacePath, projectPath, key, type, callback) {
var url = Api.buildUrl(Api.issuableTemplatePath)
issueTemplate(namespacePath, projectPath, key, type, callback) {
const url = Api.buildUrl(Api.issuableTemplatePath)
.replace(':key', key)
.replace(':type', type)
.replace(':project_path', projectPath)
.replace(':namespace_path', namespacePath);
$.ajax({
url: url,
dataType: 'json'
}).done(function(file) {
callback(null, file);
}).error(callback);
url,
dataType: 'json',
})
.done(file => callback(null, file))
.error(callback);
},
buildUrl: function(url) {
users(query, options) {
const url = Api.buildUrl(this.usersPath);
return Api.wrapAjaxCall({
url,
data: Object.assign({
search: query,
per_page: 20,
}, options),
dataType: 'json',
});
},
buildUrl(url) {
let urlRoot = '';
if (gon.relative_url_root != null) {
url = gon.relative_url_root + url;
urlRoot = gon.relative_url_root;
}
return url.replace(':version', gon.api_version);
}
return urlRoot + url.replace(':version', gon.api_version);
},
wrapAjaxCall(options) {
return new Promise((resolve, reject) => {
// jQuery 2 is not Promises/A+ compatible (missing catch)
$.ajax(options) // eslint-disable-line promise/catch-or-return
.then(data => resolve(data),
(jqXHR, textStatus, errorThrown) => {
const error = new Error(`${options.url}: ${errorThrown}`);
error.textStatus = textStatus;
reject(error);
},
);
});
},
};
window.Api = Api;
export default Api;
/* global Api */
export default class FileTemplateSelector {
constructor(mediator) {
this.mediator = mediator;
......@@ -65,4 +63,3 @@ export default class FileTemplateSelector {
this.reportSelection(opts);
}
}
/* global Api */
import Api from '../../api';
import FileTemplateSelector from '../file_template_selector';
......
/* global Api */
import Api from '../../api';
import FileTemplateSelector from '../file_template_selector';
......
/* global Api */
import Api from '../../api';
import FileTemplateSelector from '../file_template_selector';
......
/* global Api */
import Api from '../../api';
import FileTemplateSelector from '../file_template_selector';
......
......@@ -50,9 +50,9 @@ export default class BlobViewer {
if (this.copySourceBtn) {
this.copySourceBtn.addEventListener('click', () => {
if (this.copySourceBtn.classList.contains('disabled')) return;
if (this.copySourceBtn.classList.contains('disabled')) return this.copySourceBtn.blur();
this.switchToViewer('simple');
return this.switchToViewer('simple');
});
}
}
......
......@@ -6,23 +6,22 @@ import Vue from 'vue';
import VueResource from 'vue-resource';
import FilteredSearchBoards from './filtered_search_boards';
import eventHub from './eventhub';
require('./models/issue');
require('./models/label');
require('./models/list');
require('./models/milestone');
require('./models/assignee');
require('./stores/boards_store');
require('./stores/modal_store');
require('./services/board_service');
require('./mixins/modal_mixins');
require('./mixins/sortable_default_options');
require('./filters/due_date_filters');
require('./components/board');
require('./components/board_sidebar');
require('./components/new_list_dropdown');
require('./components/modal/index');
require('../vue_shared/vue_resource_interceptor');
import './models/issue';
import './models/label';
import './models/list';
import './models/milestone';
import './models/assignee';
import './stores/boards_store';
import './stores/modal_store';
import './services/board_service';
import './mixins/modal_mixins';
import './mixins/sortable_default_options';
import './filters/due_date_filters';
import './components/board';
import './components/board_sidebar';
import './components/new_list_dropdown';
import './components/modal/index';
import '../vue_shared/vue_resource_interceptor';
Vue.use(VueResource);
......
......@@ -3,9 +3,7 @@
import Vue from 'vue';
import boardList from './board_list';
import boardBlankState from './board_blank_state';
require('./board_delete');
require('./board_list');
import './board_delete';
const Store = gl.issueBoards.BoardsStore;
......
require('./issue_card_inner');
import './issue_card_inner';
const Store = gl.issueBoards.BoardsStore;
......
......@@ -7,11 +7,9 @@
import Vue from 'vue';
import eventHub from '../../sidebar/event_hub';
import AssigneeTitle from '../../sidebar/components/assignees/assignee_title';
import Assignees from '../../sidebar/components/assignees/assignees';
require('./sidebar/remove_issue');
import './sidebar/remove_issue';
const Store = gl.issueBoards.BoardsStore;
......
import Vue from 'vue';
import userAvatarLink from '../../vue_shared/components/user_avatar/user_avatar_link.vue';
import eventHub from '../eventhub';
const Store = gl.issueBoards.BoardsStore;
......@@ -38,6 +39,9 @@ gl.issueBoards.IssueCardInner = Vue.extend({
maxCounter: 99,
};
},
components: {
userAvatarLink,
},
computed: {
numberOverLimit() {
return this.issue.assignees.length - this.limitBeforeCounter;
......@@ -146,23 +150,16 @@ gl.issueBoards.IssueCardInner = Vue.extend({
</span>
</h4>
<div class="card-assignee">
<a
class="has-tooltip js-no-trigger"
:href="assigneeUrl(assignee)"
:title="assigneeUrlTitle(assignee)"
<user-avatar-link
v-for="(assignee, index) in issue.assignees"
v-if="shouldRenderAssignee(index)"
data-container="body"
data-placement="bottom"
>
<img
class="avatar avatar-inline s20"
:src="assignee.avatar"
width="20"
height="20"
:alt="avatarUrlTitle(assignee)"
/>
</a>
class="js-no-trigger"
:link-href="assigneeUrl(assignee)"
:img-alt="avatarUrlTitle(assignee)"
:img-src="assignee.avatar"
:tooltip-text="assigneeUrlTitle(assignee)"
tooltip-placement="bottom"
/>
<span
class="avatar-counter has-tooltip"
:title="assigneeCounterTooltip"
......
......@@ -2,8 +2,7 @@
/* global Flash */
import Vue from 'vue';
require('./lists_dropdown');
import './lists_dropdown';
const ModalStore = gl.issueBoards.ModalStore;
......
import Vue from 'vue';
import modalFilters from './filters';
require('./tabs');
import './tabs';
const ModalStore = gl.issueBoards.ModalStore;
......
......@@ -3,11 +3,10 @@
import Vue from 'vue';
import queryData from '../../utils/query_data';
import loadingIcon from '../../../vue_shared/components/loading_icon.vue';
require('./header');
require('./list');
require('./footer');
require('./empty_state');
import './header';
import './list';
import './footer';
import './empty_state';
const ModalStore = gl.issueBoards.ModalStore;
......
/* eslint-disable class-methods-use-this, object-shorthand, no-unused-vars, no-use-before-define, no-new, max-len, no-restricted-syntax, guard-for-in, no-continue */
require('./lib/utils/common_utils');
import './lib/utils/common_utils';
const gfmRules = {
// The filters referenced in lib/banzai/pipeline/gfm_pipeline.rb convert
......@@ -18,12 +18,12 @@ const gfmRules = {
},
},
TaskListFilter: {
'input[type=checkbox].task-list-item-checkbox'(el, text) {
'input[type=checkbox].task-list-item-checkbox'(el) {
return `[${el.checked ? 'x' : ' '}]`;
},
},
ReferenceFilter: {
'.tooltip'(el, text) {
'.tooltip'(el) {
return '';
},
'a.gfm:not([data-link=true])'(el, text) {
......@@ -39,15 +39,15 @@ const gfmRules = {
},
},
TableOfContentsFilter: {
'ul.section-nav'(el, text) {
'ul.section-nav'(el) {
return '[[_TOC_]]';
},
},
EmojiFilter: {
'img.emoji'(el, text) {
'img.emoji'(el) {
return el.getAttribute('alt');
},
'gl-emoji'(el, text) {
'gl-emoji'(el) {
return `:${el.getAttribute('data-name')}:`;
},
},
......@@ -57,13 +57,13 @@ const gfmRules = {
},
},
VideoLinkFilter: {
'.video-container'(el, text) {
'.video-container'(el) {
const videoEl = el.querySelector('video');
if (!videoEl) return false;
return CopyAsGFM.nodeToGFM(videoEl);
},
'video'(el, text) {
'video'(el) {
return `![${el.dataset.title}](${el.getAttribute('src')})`;
},
},
......@@ -74,19 +74,19 @@ const gfmRules = {
'code.code.math[data-math-style=inline]'(el, text) {
return `$\`${text}\`$`;
},
'span.katex-display span.katex-mathml'(el, text) {
'span.katex-display span.katex-mathml'(el) {
const mathAnnotation = el.querySelector('annotation[encoding="application/x-tex"]');
if (!mathAnnotation) return false;
return `\`\`\`math\n${CopyAsGFM.nodeToGFM(mathAnnotation)}\n\`\`\``;
},
'span.katex-mathml'(el, text) {
'span.katex-mathml'(el) {
const mathAnnotation = el.querySelector('annotation[encoding="application/x-tex"]');
if (!mathAnnotation) return false;
return `$\`${CopyAsGFM.nodeToGFM(mathAnnotation)}\`$`;
},
'span.katex-html'(el, text) {
'span.katex-html'(el) {
// We don't want to include the content of this element in the copied text.
return '';
},
......@@ -95,7 +95,7 @@ const gfmRules = {
},
},
SanitizationFilter: {
'a[name]:not([href]):empty'(el, text) {
'a[name]:not([href]):empty'(el) {
return el.outerHTML;
},
'dl'(el, text) {
......@@ -143,7 +143,7 @@ const gfmRules = {
},
},
MarkdownFilter: {
'br'(el, text) {
'br'(el) {
// Two spaces at the end of a line are turned into a BR
return ' ';
},
......@@ -162,7 +162,7 @@ const gfmRules = {
'blockquote'(el, text) {
return text.trim().split('\n').map(s => `> ${s}`.trim()).join('\n');
},
'img'(el, text) {
'img'(el) {
return `![${el.getAttribute('alt')}](${el.getAttribute('src')})`;
},
'a.anchor'(el, text) {
......@@ -222,10 +222,10 @@ const gfmRules = {
'sup'(el, text) {
return `^${text}`;
},
'hr'(el, text) {
'hr'(el) {
return '-----';
},
'table'(el, text) {
'table'(el) {
const theadEl = el.querySelector('thead');
const tbodyEl = el.querySelector('tbody');
if (!theadEl || !tbodyEl) return false;
......@@ -233,11 +233,11 @@ const gfmRules = {
const theadText = CopyAsGFM.nodeToGFM(theadEl);
const tbodyText = CopyAsGFM.nodeToGFM(tbodyEl);
return theadText + tbodyText;
return [theadText, tbodyText].join('\n');
},
'thead'(el, text) {
const cells = _.map(el.querySelectorAll('th'), (cell) => {
let chars = CopyAsGFM.nodeToGFM(cell).trim().length + 2;
let chars = CopyAsGFM.nodeToGFM(cell).length + 2;
let before = '';
let after = '';
......@@ -262,10 +262,15 @@ const gfmRules = {
return before + middle + after;
});
return `${text}|${cells.join('|')}|`;
const separatorRow = `|${cells.join('|')}|`;
return [text, separatorRow].join('\n');
},
'tr'(el, text) {
const cells = _.map(el.querySelectorAll('td, th'), cell => CopyAsGFM.nodeToGFM(cell).trim());
'tr'(el) {
const cellEls = el.querySelectorAll('td, th');
if (cellEls.length === 0) return false;
const cells = _.map(cellEls, cell => CopyAsGFM.nodeToGFM(cell));
return `| ${cells.join(' | ')} |`;
},
},
......@@ -273,12 +278,12 @@ const gfmRules = {
class CopyAsGFM {
constructor() {
$(document).on('copy', '.md, .wiki', (e) => { this.copyAsGFM(e, CopyAsGFM.transformGFMSelection); });
$(document).on('copy', 'pre.code.highlight, .diff-content .line_content', (e) => { this.copyAsGFM(e, CopyAsGFM.transformCodeSelection); });
$(document).on('paste', '.js-gfm-input', this.pasteGFM.bind(this));
$(document).on('copy', '.md, .wiki', (e) => { CopyAsGFM.copyAsGFM(e, CopyAsGFM.transformGFMSelection); });
$(document).on('copy', 'pre.code.highlight, .diff-content .line_content', (e) => { CopyAsGFM.copyAsGFM(e, CopyAsGFM.transformCodeSelection); });
$(document).on('paste', '.js-gfm-input', CopyAsGFM.pasteGFM);
}
copyAsGFM(e, transformer) {
static copyAsGFM(e, transformer) {
const clipboardData = e.originalEvent.clipboardData;
if (!clipboardData) return;
......@@ -292,26 +297,59 @@ class CopyAsGFM {
e.stopPropagation();
clipboardData.setData('text/plain', el.textContent);
clipboardData.setData('text/x-gfm', CopyAsGFM.nodeToGFM(el));
clipboardData.setData('text/x-gfm', this.nodeToGFM(el));
}
pasteGFM(e) {
static pasteGFM(e) {
const clipboardData = e.originalEvent.clipboardData;
if (!clipboardData) return;
const text = clipboardData.getData('text/plain');
const gfm = clipboardData.getData('text/x-gfm');
if (!gfm) return;
e.preventDefault();
window.gl.utils.insertText(e.target, gfm);
window.gl.utils.insertText(e.target, (textBefore, textAfter) => {
// If the text before the cursor contains an odd number of backticks,
// we are either inside an inline code span that starts with 1 backtick
// or a code block that starts with 3 backticks.
// This logic still holds when there are one or more _closed_ code spans
// or blocks that will have 2 or 6 backticks.
// This will break down when the actual code block contains an uneven
// number of backticks, but this is a rare edge case.
const backtickMatch = textBefore.match(/`/g);
const insideCodeBlock = backtickMatch && (backtickMatch.length % 2) === 1;
if (insideCodeBlock) {
return text;
}
return gfm;
});
}
static transformGFMSelection(documentFragment) {
// If the documentFragment contains more than just Markdown, don't copy as GFM.
if (documentFragment.querySelector('.md, .wiki')) return null;
const gfmEls = documentFragment.querySelectorAll('.md, .wiki');
switch (gfmEls.length) {
case 0: {
return documentFragment;
}
case 1: {
return gfmEls[0];
}
default: {
const allGfmEl = document.createElement('div');
for (let i = 0; i < gfmEls.length; i += 1) {
const lineEl = gfmEls[i];
allGfmEl.appendChild(lineEl);
allGfmEl.appendChild(document.createTextNode('\n\n'));
}
return documentFragment;
return allGfmEl;
}
}
}
static transformCodeSelection(documentFragment) {
......@@ -343,7 +381,7 @@ class CopyAsGFM {
return codeEl;
}
static nodeToGFM(node) {
static nodeToGFM(node, respectWhitespaceParam = false) {
if (node.nodeType === Node.COMMENT_NODE) {
return '';
}
......@@ -352,7 +390,9 @@ class CopyAsGFM {
return node.textContent;
}
const text = this.innerGFM(node);
const respectWhitespace = respectWhitespaceParam || (node.nodeName === 'PRE' || node.nodeName === 'CODE');
const text = this.innerGFM(node, respectWhitespace);
if (node.nodeType === Node.DOCUMENT_FRAGMENT_NODE) {
return text;
......@@ -366,7 +406,17 @@ class CopyAsGFM {
if (!window.gl.utils.nodeMatchesSelector(node, selector)) continue;
const result = func(node, text);
let result;
if (func.length === 2) {
// if `func` takes 2 arguments, it depends on text.
// if there is no text, we don't need to generate GFM for this node.
if (text.length === 0) continue;
result = func(node, text);
} else {
result = func(node);
}
if (result === false) continue;
return result;
......@@ -376,7 +426,7 @@ class CopyAsGFM {
return text;
}
static innerGFM(parentNode) {
static innerGFM(parentNode, respectWhitespace = false) {
const nodes = parentNode.childNodes;
const clonedParentNode = parentNode.cloneNode(true);
......@@ -386,13 +436,19 @@ class CopyAsGFM {
const node = nodes[i];
const clonedNode = clonedNodes[i];
const text = this.nodeToGFM(node);
const text = this.nodeToGFM(node, respectWhitespace);
// `clonedNode.replaceWith(text)` is not yet widely supported
clonedNode.parentNode.replaceChild(document.createTextNode(text), clonedNode);
}
return clonedParentNode.innerText || clonedParentNode.textContent;
let nodeText = clonedParentNode.innerText || clonedParentNode.textContent;
if (!respectWhitespace) {
nodeText = nodeText.trim();
}
return nodeText;
}
}
......
/* eslint-disable func-names, space-before-function-paren, prefer-arrow-callback, comma-dangle, prefer-template, quotes, no-param-reassign, wrap-iife, max-len */
/* global Api */
import Api from './api';
class CreateLabelDropdown {
constructor ($el, namespacePath, projectPath) {
......
/* eslint-disable no-param-reassign */
import Vue from 'vue';
import userAvatarImage from '../../vue_shared/components/user_avatar/user_avatar_image.vue';
const global = window.gl || (window.gl = {});
global.cycleAnalytics = global.cycleAnalytics || {};
......@@ -10,6 +11,9 @@ global.cycleAnalytics.StageCodeComponent = Vue.extend({
items: Array,
stage: Object,
},
components: {
userAvatarImage,
},
template: `
<div>
<div class="events-description">
......@@ -19,7 +23,8 @@ global.cycleAnalytics.StageCodeComponent = Vue.extend({
<ul class="stage-event-list">
<li v-for="mergeRequest in items" class="stage-event-item">
<div class="item-details">
<img class="avatar" :src="mergeRequest.author.avatarUrl">
<!-- FIXME: Pass an alt attribute here for accessibility -->
<user-avatar-image :img-src="mergeRequest.author.avatarUrl"/>
<h5 class="item-title merge-merquest-title">
<a :href="mergeRequest.url">
{{ mergeRequest.title }}
......@@ -28,11 +33,11 @@ global.cycleAnalytics.StageCodeComponent = Vue.extend({
<a :href="mergeRequest.url" class="issue-link">!{{ mergeRequest.iid }}</a>
&middot;
<span>
{{ __('OpenedNDaysAgo|Opened') }}
{{ s__('OpenedNDaysAgo|Opened') }}
<a :href="mergeRequest.url" class="issue-date">{{ mergeRequest.createdAt }}</a>
</span>
<span>
{{ __('ByAuthor|by') }}
{{ s__('ByAuthor|by') }}
<a :href="mergeRequest.author.webUrl" class="issue-author-link">{{ mergeRequest.author.name }}</a>
</span>
</div>
......
/* eslint-disable no-param-reassign */
import Vue from 'vue';
import userAvatarImage from '../../vue_shared/components/user_avatar/user_avatar_image.vue';
const global = window.gl || (window.gl = {});
global.cycleAnalytics = global.cycleAnalytics || {};
......@@ -10,6 +10,9 @@ global.cycleAnalytics.StageIssueComponent = Vue.extend({
items: Array,
stage: Object,
},
components: {
userAvatarImage,
},
template: `
<div>
<div class="events-description">
......@@ -19,7 +22,8 @@ global.cycleAnalytics.StageIssueComponent = Vue.extend({
<ul class="stage-event-list">
<li v-for="issue in items" class="stage-event-item">
<div class="item-details">
<img class="avatar" :src="issue.author.avatarUrl">
<!-- FIXME: Pass an alt attribute here for accessibility -->
<user-avatar-image :img-src="issue.author.avatarUrl"/>
<h5 class="item-title issue-title">
<a class="issue-title" :href="issue.url">
{{ issue.title }}
......@@ -28,11 +32,11 @@ global.cycleAnalytics.StageIssueComponent = Vue.extend({
<a :href="issue.url" class="issue-link">#{{ issue.iid }}</a>
&middot;
<span>
{{ __('OpenedNDaysAgo|Opened') }}
{{ s__('OpenedNDaysAgo|Opened') }}
<a :href="issue.url" class="issue-date">{{ issue.createdAt }}</a>
</span>
<span>
{{ __('ByAuthor|by') }}
{{ s__('ByAuthor|by') }}
<a :href="issue.author.webUrl" class="issue-author-link">
{{ issue.author.name }}
</a>
......
/* eslint-disable no-param-reassign */
import Vue from 'vue';
import userAvatarImage from '../../vue_shared/components/user_avatar/user_avatar_image.vue';
import iconCommit from '../svg/icon_commit.svg';
const global = window.gl || (window.gl = {});
......@@ -10,11 +11,12 @@ global.cycleAnalytics.StagePlanComponent = Vue.extend({
items: Array,
stage: Object,
},
components: {
userAvatarImage,
},
data() {
return { iconCommit };
},
template: `
<div>
<div class="events-description">
......@@ -24,17 +26,18 @@ global.cycleAnalytics.StagePlanComponent = Vue.extend({
<ul class="stage-event-list">
<li v-for="commit in items" class="stage-event-item">
<div class="item-details item-conmmit-component">
<img class="avatar" :src="commit.author.avatarUrl">
<!-- FIXME: Pass an alt attribute here for accessibility -->
<user-avatar-image :img-src="commit.author.avatarUrl"/>
<h5 class="item-title commit-title">
<a :href="commit.commitUrl">
{{ commit.title }}
</a>
</h5>
<span>
{{ __('FirstPushedBy|First') }}
{{ s__('FirstPushedBy|First') }}
<span class="commit-icon">${iconCommit}</span>
<a :href="commit.commitUrl" class="commit-hash-link commit-sha">{{ commit.shortSha }}</a>
{{ __('FirstPushedBy|pushed by') }}
{{ s__('FirstPushedBy|pushed by') }}
<a :href="commit.author.webUrl" class="commit-author-link">
{{ commit.author.name }}
</a>
......
/* eslint-disable no-param-reassign */
import Vue from 'vue';
import userAvatarImage from '../../vue_shared/components/user_avatar/user_avatar_image.vue';
const global = window.gl || (window.gl = {});
global.cycleAnalytics = global.cycleAnalytics || {};
......@@ -10,6 +10,9 @@ global.cycleAnalytics.StageProductionComponent = Vue.extend({
items: Array,
stage: Object,
},
components: {
userAvatarImage,
},
template: `
<div>
<div class="events-description">
......@@ -19,7 +22,8 @@ global.cycleAnalytics.StageProductionComponent = Vue.extend({
<ul class="stage-event-list">
<li v-for="issue in items" class="stage-event-item">
<div class="item-details">
<img class="avatar" :src="issue.author.avatarUrl">
<!-- FIXME: Pass an alt attribute here for accessibility -->
<user-avatar-image :img-src="issue.author.avatarUrl"/>
<h5 class="item-title issue-title">
<a class="issue-title" :href="issue.url">
{{ issue.title }}
......@@ -28,11 +32,11 @@ global.cycleAnalytics.StageProductionComponent = Vue.extend({
<a :href="issue.url" class="issue-link">#{{ issue.iid }}</a>
&middot;
<span>
{{ __('OpenedNDaysAgo|Opened') }}
{{ s__('OpenedNDaysAgo|Opened') }}
<a :href="issue.url" class="issue-date">{{ issue.createdAt }}</a>
</span>
<span>
{{ __('ByAuthor|by') }}
{{ s__('ByAuthor|by') }}
<a :href="issue.author.webUrl" class="issue-author-link">
{{ issue.author.name }}
</a>
......
/* eslint-disable no-param-reassign */
import Vue from 'vue';
import userAvatarImage from '../../vue_shared/components/user_avatar/user_avatar_image.vue';
const global = window.gl || (window.gl = {});
global.cycleAnalytics = global.cycleAnalytics || {};
......@@ -10,6 +10,9 @@ global.cycleAnalytics.StageReviewComponent = Vue.extend({
items: Array,
stage: Object,
},
components: {
userAvatarImage,
},
template: `
<div>
<div class="events-description">
......@@ -19,7 +22,8 @@ global.cycleAnalytics.StageReviewComponent = Vue.extend({
<ul class="stage-event-list">
<li v-for="mergeRequest in items" class="stage-event-item">
<div class="item-details">
<img class="avatar" :src="mergeRequest.author.avatarUrl">
<!-- FIXME: Pass an alt attribute here for accessibility -->
<user-avatar-image :img-src="mergeRequest.author.avatarUrl"/>
<h5 class="item-title merge-merquest-title">
<a :href="mergeRequest.url">
{{ mergeRequest.title }}
......@@ -28,11 +32,11 @@ global.cycleAnalytics.StageReviewComponent = Vue.extend({
<a :href="mergeRequest.url" class="issue-link">!{{ mergeRequest.iid }}</a>
&middot;
<span>
{{ __('OpenedNDaysAgo|Opened') }}
{{ s__('OpenedNDaysAgo|Opened') }}
<a :href="mergeRequest.url" class="issue-date">{{ mergeRequest.createdAt }}</a>
</span>
<span>
{{ __('ByAuthor|by') }}
{{ s__('ByAuthor|by') }}
<a :href="mergeRequest.author.webUrl" class="issue-author-link">{{ mergeRequest.author.name }}</a>
</span>
<template v-if="mergeRequest.state === 'closed'">
......
/* eslint-disable no-param-reassign */
import Vue from 'vue';
import userAvatarImage from '../../vue_shared/components/user_avatar/user_avatar_image.vue';
import iconBranch from '../svg/icon_branch.svg';
const global = window.gl || (window.gl = {});
......@@ -13,6 +14,9 @@ global.cycleAnalytics.StageStagingComponent = Vue.extend({
data() {
return { iconBranch };
},
components: {
userAvatarImage,
},
template: `
<div>
<div class="events-description">
......@@ -22,7 +26,8 @@ global.cycleAnalytics.StageStagingComponent = Vue.extend({
<ul class="stage-event-list">
<li v-for="build in items" class="stage-event-item item-build-component">
<div class="item-details">
<img class="avatar" :src="build.author.avatarUrl">
<!-- FIXME: Pass an alt attribute here for accessibility -->
<user-avatar-image :img-src="build.author.avatarUrl"/>
<h5 class="item-title">
<a :href="build.url" class="pipeline-id">#{{ build.id }}</a>
<i class="fa fa-code-fork"></i>
......@@ -32,7 +37,7 @@ global.cycleAnalytics.StageStagingComponent = Vue.extend({
</h5>
<span>
<a :href="build.url" class="build-date">{{ build.date }}</a>
{{ __('ByAuthor|by') }}
{{ s__('ByAuthor|by') }}
<a :href="build.author.webUrl" class="issue-author-link">
{{ build.author.name }}
</a>
......
......@@ -4,18 +4,16 @@ import Vue from 'vue';
import Cookies from 'js-cookie';
import Translate from '../vue_shared/translate';
import LimitWarningComponent from './components/limit_warning_component';
require('./components/stage_code_component');
require('./components/stage_issue_component');
require('./components/stage_plan_component');
require('./components/stage_production_component');
require('./components/stage_review_component');
require('./components/stage_staging_component');
require('./components/stage_test_component');
require('./components/total_time_component');
require('./cycle_analytics_service');
require('./cycle_analytics_store');
require('./default_event_objects');
import './components/stage_code_component';
import './components/stage_issue_component';
import './components/stage_plan_component';
import './components/stage_production_component';
import './components/stage_review_component';
import './components/stage_staging_component';
import './components/stage_test_component';
import './components/total_time_component';
import './cycle_analytics_service';
import './cycle_analytics_store';
Vue.use(Translate);
......
/* eslint-disable no-param-reassign */
import { __ } from '../locale';
require('../lib/utils/text_utility');
const DEFAULT_EVENT_OBJECTS = require('./default_event_objects');
import { __ } from '../locale';
import '../lib/utils/text_utility';
import DEFAULT_EVENT_OBJECTS from './default_event_objects';
const global = window.gl || (window.gl = {});
global.cycleAnalytics = global.cycleAnalytics || {};
......
module.exports = {
export default {
issue: {
created_at: '',
url: '',
......
/* eslint-disable class-methods-use-this */
require('./lib/utils/url_utility');
import './lib/utils/url_utility';
const UNFOLD_COUNT = 20;
let isBound = false;
......
......@@ -3,6 +3,7 @@
import Vue from 'vue';
import collapseIcon from '../icons/collapse_icon.svg';
import userAvatarImage from '../../vue_shared/components/user_avatar/user_avatar_image.vue';
const DiffNoteAvatars = Vue.extend({
props: ['discussionId'],
......@@ -15,22 +16,24 @@ const DiffNoteAvatars = Vue.extend({
collapseIcon,
};
},
components: {
userAvatarImage,
},
template: `
<div class="diff-comment-avatar-holders"
v-show="notesCount !== 0">
<div v-if="!isVisible">
<img v-for="note in notesSubset"
class="avatar diff-comment-avatar has-tooltip js-diff-comment-avatar"
width="19"
height="19"
role="button"
data-container="body"
data-placement="top"
data-html="true"
<!-- FIXME: Pass an alt attribute here for accessibility -->
<user-avatar-image
v-for="note in notesSubset"
class="diff-comment-avatar js-diff-comment-avatar"
@click.native="clickedAvatar($event)"
:img-src="note.authorAvatar"
:tooltip-text="getTooltipText(note)"
:data-line-type="lineType"
:title="note.authorName + ': ' + note.noteTruncated"
:src="note.authorAvatar"
@click="clickedAvatar($event)" />
:size="19"
data-html="true"
/>
<span v-if="notesCount > shownAvatars"
class="diff-comments-more-count has-tooltip js-diff-comment-avatar"
data-container="body"
......@@ -150,6 +153,9 @@ const DiffNoteAvatars = Vue.extend({
setDiscussionVisible() {
this.isVisible = $(`.diffs .notes[data-discussion-id="${this.discussion.id}"]`).is(':visible');
},
getTooltipText(note) {
return `${note.authorName}: ${note.noteTruncated}`;
},
},
});
......
......@@ -2,19 +2,18 @@
/* global ResolveCount */
import Vue from 'vue';
require('./models/discussion');
require('./models/note');
require('./stores/comments');
require('./services/resolve');
require('./mixins/discussion');
require('./components/comment_resolve_btn');
require('./components/jump_to_discussion');
require('./components/resolve_btn');
require('./components/resolve_count');
require('./components/resolve_discussion_btn');
require('./components/diff_note_avatars');
require('./components/new_issue_for_discussion');
import './models/discussion';
import './models/note';
import './stores/comments';
import './services/resolve';
import './mixins/discussion';
import './components/comment_resolve_btn';
import './components/jump_to_discussion';
import './components/resolve_btn';
import './components/resolve_count';
import './components/resolve_discussion_btn';
import './components/diff_note_avatars';
import './components/new_issue_for_discussion';
$(() => {
const projectPath = document.querySelector('.merge-request').dataset.projectPath;
......
......@@ -3,11 +3,7 @@
/* global CommentsStore */
import Vue from 'vue';
import VueResource from 'vue-resource';
require('../../vue_shared/vue_resource_interceptor');
Vue.use(VueResource);
import '../../vue_shared/vue_resource_interceptor';
window.gl = window.gl || {};
......
......@@ -34,13 +34,13 @@
/* global ShortcutsWiki */
import Issue from './issue';
import BindInOut from './behaviors/bind_in_out';
import DeleteModal from './branches/branches_delete_modal';
import Group from './group';
import GroupName from './group_name';
import GroupsList from './groups_list';
import ProjectsList from './projects_list';
import setupProjectEdit from './project_edit';
import MiniPipelineGraph from './mini_pipeline_graph_dropdown';
import BlobLinePermalinkUpdater from './blob/blob_line_permalink_updater';
import Landing from './landing';
......@@ -54,8 +54,7 @@ import AutoWidthDropdownSelect from './issuable/auto_width_dropdown_select';
import UsersSelect from './users_select';
import RefSelectDropdown from './ref_select_dropdown';
import GfmAutoComplete from './gfm_auto_complete';
const ShortcutsBlob = require('./shortcuts_blob');
import ShortcutsBlob from './shortcuts_blob';
(function() {
var Dispatcher;
......@@ -124,7 +123,7 @@ const ShortcutsBlob = require('./shortcuts_blob');
break;
case 'projects:merge_requests:index':
case 'projects:issues:index':
if (gl.FilteredSearchManager) {
if (gl.FilteredSearchManager && document.querySelector('.filtered-search')) {
new gl.FilteredSearchManager(page === 'projects:issues:index' ? 'issues' : 'merge_requests');
}
Issuable.init();
......@@ -261,9 +260,14 @@ const ShortcutsBlob = require('./shortcuts_blob');
new NotificationsForm();
if ($('#tree-slider').length) {
new TreeView();
}
if ($('.blob-viewer').length) {
new BlobViewer();
}
break;
case 'projects:edit':
setupProjectEdit();
break;
case 'projects:pipelines:builds':
case 'projects:pipelines:failures':
case 'projects:pipelines:show':
......
/* eslint-disable */
import AjaxCache from '~/lib/utils/ajax_cache';
const Ajax = {
_loadUrlData: function _loadUrlData(url) {
var self = this;
return new Promise(function(resolve, reject) {
var xhr = new XMLHttpRequest;
xhr.open('GET', url, true);
xhr.onreadystatechange = function () {
if(xhr.readyState === XMLHttpRequest.DONE) {
if (xhr.status === 200) {
var data = JSON.parse(xhr.responseText);
self.cache[url] = data;
return resolve(data);
} else {
return reject([xhr.responseText, xhr.status]);
}
}
};
xhr.send();
});
},
_loadData: function _loadData(data, config, self) {
if (config.loadingTemplate) {
var dataLoadingTemplate = self.hook.list.list.querySelector('[data-loading-template]');
......@@ -31,7 +14,6 @@ const Ajax = {
init: function init(hook) {
var self = this;
self.destroyed = false;
self.cache = self.cache || {};
var config = hook.config.Ajax;
this.hook = hook;
if (!config || !config.endpoint || !config.method) {
......@@ -48,14 +30,10 @@ const Ajax = {
this.listTemplate = dynamicList.outerHTML;
dynamicList.outerHTML = loadingTemplate.outerHTML;
}
if (self.cache[config.endpoint]) {
self._loadData(self.cache[config.endpoint], config, self);
} else {
this._loadUrlData(config.endpoint)
.then(function(d) {
self._loadData(d, config, self);
}, config.onError).catch(config.onError);
}
AjaxCache.retrieve(config.endpoint)
.then((data) => self._loadData(data, config, self))
.catch(config.onError);
},
destroy: function() {
this.destroyed = true;
......
/* eslint-disable */
import AjaxCache from '../../lib/utils/ajax_cache';
const AjaxFilter = {
init: function(hook) {
......@@ -58,50 +59,24 @@ const AjaxFilter = {
this.loading = true;
var params = config.params || {};
params[config.searchKey] = searchValue;
var self = this;
self.cache = self.cache || {};
var url = config.endpoint + this.buildParams(params);
var urlCachedData = self.cache[url];
if (urlCachedData) {
self._loadData(urlCachedData, config, self);
} else {
this._loadUrlData(url)
.then(function(data) {
self._loadData(data, config, self);
}, config.onError).catch(config.onError);
}
return AjaxCache.retrieve(url)
.then((data) => {
this._loadData(data, config);
})
.catch(config.onError);
},
_loadUrlData: function _loadUrlData(url) {
var self = this;
return new Promise(function(resolve, reject) {
var xhr = new XMLHttpRequest;
xhr.open('GET', url, true);
xhr.onreadystatechange = function () {
if(xhr.readyState === XMLHttpRequest.DONE) {
if (xhr.status === 200) {
var data = JSON.parse(xhr.responseText);
self.cache[url] = data;
return resolve(data);
} else {
return reject([xhr.responseText, xhr.status]);
}
}
};
xhr.send();
});
},
_loadData: function _loadData(data, config, self) {
const list = self.hook.list;
_loadData(data, config) {
const list = this.hook.list;
if (config.loadingTemplate && list.data === undefined ||
list.data.length === 0) {
const dataLoadingTemplate = list.list.querySelector('[data-loading-template]');
if (dataLoadingTemplate) {
dataLoadingTemplate.outerHTML = self.listTemplate;
dataLoadingTemplate.outerHTML = this.listTemplate;
}
}
if (!self.destroyed) {
if (!this.destroyed) {
var hookListChildren = list.list.children;
var onlyDynamicList = hookListChildren.length === 1 && hookListChildren[0].hasAttribute('data-dynamic');
if (onlyDynamicList && data.length === 0) {
......@@ -109,7 +84,7 @@ const AjaxFilter = {
}
list.setData.call(list, data);
}
self.notLoading();
this.notLoading();
list.currentIndex = 0;
},
......
/* eslint-disable func-names, space-before-function-paren, wrap-iife, max-len, one-var, no-var, one-var-declaration-per-line, no-unused-vars, camelcase, quotes, no-useless-concat, prefer-template, quote-props, comma-dangle, object-shorthand, consistent-return, prefer-arrow-callback */
/* global Dropzone */
require('./preview_markdown');
import './preview_markdown';
window.DropzoneInput = (function() {
function DropzoneInput(form) {
......
<script>
import Timeago from 'timeago.js';
import _ from 'underscore';
import userAvatarLink from '../../vue_shared/components/user_avatar/user_avatar_link.vue';
import '../../lib/utils/text_utility';
import ActionsComponent from './environment_actions.vue';
import ExternalUrlComponent from './environment_external_url.vue';
......@@ -20,6 +21,7 @@ const timeagoInstance = new Timeago();
export default {
components: {
userAvatarLink,
'commit-component': CommitComponent,
'actions-component': ActionsComponent,
'external-url-component': ExternalUrlComponent,
......@@ -468,15 +470,13 @@ export default {
<span v-if="!model.isFolder && deploymentHasUser">
by
<a
:href="deploymentUser.web_url"
class="js-deploy-user-container">
<img
class="avatar has-tooltip s20"
:src="deploymentUser.avatar_url"
:alt="userImageAltDescription"
:title="deploymentUser.username" />
</a>
<user-avatar-link
class="js-deploy-user-container"
:link-href="deploymentUser.web_url"
:img-src="deploymentUser.avatar_url"
:img-alt="userImageAltDescription"
:tooltip-text="deploymentUser.username"
/>
</span>
</td>
......
......@@ -13,13 +13,17 @@ export default {
required: false,
default: true,
},
allowedKeys: {
type: Array,
required: true,
},
},
computed: {
processedItems() {
return this.items.map((item) => {
const { tokens, searchToken }
= gl.FilteredSearchTokenizer.processTokens(item);
= gl.FilteredSearchTokenizer.processTokens(item, this.allowedKeys);
const resultantTokens = tokens.map(token => ({
prefix: `${token.key}:`,
......
import Filter from '~/droplab/plugins/filter';
require('./filtered_search_dropdown');
import './filtered_search_dropdown';
class DropdownHint extends gl.FilteredSearchDropdown {
constructor(droplab, dropdown, input, filter) {
constructor(droplab, dropdown, input, tokenKeys, filter) {
super(droplab, dropdown, input, filter);
this.config = {
Filter: {
template: 'hint',
filterFunction: gl.DropdownUtils.filterHint.bind(null, input),
filterFunction: gl.DropdownUtils.filterHint.bind(null, {
input,
allowedKeys: tokenKeys.getKeys(),
}),
},
};
this.tokenKeys = tokenKeys;
}
itemClicked(e) {
......@@ -53,20 +56,13 @@ class DropdownHint extends gl.FilteredSearchDropdown {
}
renderContent() {
const dropdownData = [];
[].forEach.call(this.input.closest('.filtered-search-box-input-container').querySelectorAll('.dropdown-menu'), (dropdownMenu) => {
const { icon, hint, tag, type } = dropdownMenu.dataset;
if (icon && hint && tag) {
dropdownData.push(
Object.assign({
icon: `fa-${icon}`,
hint,
tag: `<${tag}>`,
}, type && { type }),
);
}
});
const dropdownData = gl.FilteredSearchTokenKeys.get()
.map(tokenKey => ({
icon: `fa-${tokenKey.icon}`,
hint: tokenKey.key,
tag: `<${tokenKey.symbol}${tokenKey.key}>`,
type: tokenKey.type,
}));
this.droplab.changeHookList(this.hookId, this.dropdown, [Filter], this.config);
this.droplab.setData(this.hookId, dropdownData);
......
......@@ -2,11 +2,10 @@
import Ajax from '~/droplab/plugins/ajax';
import Filter from '~/droplab/plugins/filter';
require('./filtered_search_dropdown');
import './filtered_search_dropdown';
class DropdownNonUser extends gl.FilteredSearchDropdown {
constructor(droplab, dropdown, input, filter, endpoint, symbol) {
constructor(droplab, dropdown, input, tokenKeys, filter, endpoint, symbol) {
super(droplab, dropdown, input, filter);
this.symbol = symbol;
this.config = {
......
/* global Flash */
import AjaxFilter from '~/droplab/plugins/ajax_filter';
require('./filtered_search_dropdown');
import './filtered_search_dropdown';
class DropdownUser extends gl.FilteredSearchDropdown {
constructor(droplab, dropdown, input, filter) {
constructor(droplab, dropdown, input, tokenKeys, filter) {
super(droplab, dropdown, input, filter);
this.config = {
AjaxFilter: {
......@@ -26,6 +25,7 @@ class DropdownUser extends gl.FilteredSearchDropdown {
},
},
};
this.tokenKeys = tokenKeys;
}
itemClicked(e) {
......@@ -44,7 +44,7 @@ class DropdownUser extends gl.FilteredSearchDropdown {
getSearchInput() {
const query = gl.DropdownUtils.getSearchInput(this.input);
const { lastToken } = gl.FilteredSearchTokenizer.processTokens(query);
const { lastToken } = gl.FilteredSearchTokenizer.processTokens(query, this.tokenKeys.get());
let value = lastToken || '';
......
......@@ -50,10 +50,12 @@ class DropdownUtils {
return updatedItem;
}
static filterHint(input, item) {
static filterHint(config, item) {
const { input, allowedKeys } = config;
const updatedItem = item;
const searchInput = gl.DropdownUtils.getSearchQuery(input);
const { lastToken, tokens } = gl.FilteredSearchTokenizer.processTokens(searchInput);
const { lastToken, tokens } =
gl.FilteredSearchTokenizer.processTokens(searchInput, allowedKeys);
const lastKey = lastToken.key || lastToken || '';
const allowMultiple = item.type === 'array';
const itemInExistingTokens = tokens.some(t => t.key === item.hint);
......
require('./dropdown_hint');
require('./dropdown_non_user');
require('./dropdown_user');
require('./dropdown_utils');
require('./filtered_search_dropdown_manager');
require('./filtered_search_dropdown');
require('./filtered_search_manager');
require('./filtered_search_token_keys');
require('./filtered_search_tokenizer');
require('./filtered_search_visual_tokens');
import './dropdown_hint';
import './dropdown_non_user';
import './dropdown_user';
import './dropdown_utils';
import './filtered_search_dropdown_manager';
import './filtered_search_dropdown';
import './filtered_search_manager';
import './filtered_search_token_keys';
import './filtered_search_tokenizer';
import './filtered_search_visual_tokens';
......@@ -2,10 +2,10 @@ import DropLab from '~/droplab/drop_lab';
import FilteredSearchContainer from './container';
class FilteredSearchDropdownManager {
constructor(baseEndpoint = '', page) {
constructor(baseEndpoint = '', tokenizer, page) {
this.container = FilteredSearchContainer.container;
this.baseEndpoint = baseEndpoint.replace(/\/$/, '');
this.tokenizer = gl.FilteredSearchTokenizer;
this.tokenizer = tokenizer;
this.filteredSearchTokenKeys = gl.FilteredSearchTokenKeys;
this.filteredSearchInput = this.container.querySelector('.filtered-search');
this.page = page;
......@@ -98,7 +98,8 @@ class FilteredSearchDropdownManager {
if (!mappingKey.reference) {
const dl = this.droplab;
const defaultArguments = [null, dl, element, this.filteredSearchInput, key];
const defaultArguments =
[null, dl, element, this.filteredSearchInput, this.filteredSearchTokenKeys, key];
const glArguments = defaultArguments.concat(mappingKey.extraArguments || []);
// Passing glArguments to `new gl[glClass](<arguments>)`
......@@ -141,7 +142,8 @@ class FilteredSearchDropdownManager {
setDropdown() {
const query = gl.DropdownUtils.getSearchQuery(true);
const { lastToken, searchToken } = this.tokenizer.processTokens(query);
const { lastToken, searchToken } =
this.tokenizer.processTokens(query, this.filteredSearchTokenKeys.getKeys());
if (this.currentDropdown) {
this.updateCurrentDropdownOffset();
......
......@@ -15,6 +15,7 @@ class FilteredSearchManager {
this.recentSearchesStore = new RecentSearchesStore({
isLocalStorageAvailable: RecentSearchesService.isAvailable(),
allowedKeys: this.filteredSearchTokenKeys.getKeys(),
});
const searchHistoryDropdownElement = document.querySelector('.js-filtered-search-history-dropdown');
const projectPath = searchHistoryDropdownElement ?
......@@ -46,7 +47,7 @@ class FilteredSearchManager {
if (this.filteredSearchInput) {
this.tokenizer = gl.FilteredSearchTokenizer;
this.dropdownManager = new gl.FilteredSearchDropdownManager(this.filteredSearchInput.getAttribute('data-base-endpoint') || '', page);
this.dropdownManager = new gl.FilteredSearchDropdownManager(this.filteredSearchInput.getAttribute('data-base-endpoint') || '', this.tokenizer, page);
this.recentSearchesRoot = new RecentSearchesRoot(
this.recentSearchesStore,
......@@ -318,7 +319,7 @@ class FilteredSearchManager {
handleInputVisualToken() {
const input = this.filteredSearchInput;
const { tokens, searchToken }
= gl.FilteredSearchTokenizer.processTokens(input.value);
= this.tokenizer.processTokens(input.value, this.filteredSearchTokenKeys.getKeys());
const { isLastVisualTokenValid }
= gl.FilteredSearchVisualTokens.getLastVisualTokenBeforeInput();
......@@ -444,7 +445,7 @@ class FilteredSearchManager {
this.saveCurrentSearchQuery();
const { tokens, searchToken }
= this.tokenizer.processTokens(searchQuery);
= this.tokenizer.processTokens(searchQuery, this.filteredSearchTokenKeys.getKeys());
const currentState = gl.utils.getParameterByName('state') || 'opened';
paths.push(`state=${currentState}`);
......
......@@ -3,21 +3,25 @@ const tokenKeys = [{
type: 'string',
param: 'username',
symbol: '@',
icon: 'pencil',
}, {
key: 'assignee',
type: 'string',
param: 'username',
symbol: '@',
icon: 'user',
}, {
key: 'milestone',
type: 'string',
param: 'title',
symbol: '%',
icon: 'clock-o',
}, {
key: 'label',
type: 'array',
param: 'name[]',
symbol: '~',
icon: 'tag',
}];
const alternativeTokenKeys = [{
......@@ -56,6 +60,10 @@ class FilteredSearchTokenKeys {
return tokenKeys;
}
static getKeys() {
return tokenKeys.map(i => i.key);
}
static getAlternatives() {
return alternativeTokenKeys;
}
......
require('./filtered_search_token_keys');
import './filtered_search_token_keys';
class FilteredSearchTokenizer {
static processTokens(input) {
const allowedKeys = gl.FilteredSearchTokenKeys.get().map(i => i.key);
static processTokens(input, allowedKeys) {
// Regex extracts `(token):(symbol)(value)`
// Values that start with a double quote must end in a double quote (same for single)
const tokenRegex = new RegExp(`(${allowedKeys.join('|')}):([~%@]?)(?:('[^']*'{0,1})|("[^"]*"{0,1})|(\\S+))`, 'g');
......
......@@ -37,6 +37,7 @@ class RecentSearchesRoot {
<recent-searches-dropdown-content
:items="recentSearches"
:is-local-storage-available="isLocalStorageAvailable"
:allowed-keys="allowedKeys"
/>
`,
components: {
......
import _ from 'underscore';
class RecentSearchesStore {
constructor(initialState = {}) {
constructor(initialState = {}, allowedKeys) {
this.state = Object.assign({
isLocalStorageAvailable: true,
recentSearches: [],
allowedKeys,
}, initialState);
}
......
/* eslint-disable comma-dangle, class-methods-use-this, max-len, space-before-function-paren, arrow-parens, no-param-reassign */
require('./gl_field_error');
import './gl_field_error';
const customValidationFlag = 'gl-field-error-ignore';
......
......@@ -7,9 +7,10 @@ import GfmAutoComplete from './gfm_auto_complete';
window.gl = window.gl || {};
function GLForm(form) {
function GLForm(form, enableGFM = false) {
this.form = form;
this.textarea = this.form.find('textarea.js-gfm-input');
this.enableGFM = enableGFM;
// Before we start, we should clean up any previous data for this form
this.destroy();
// Setup the form
......@@ -32,8 +33,14 @@ GLForm.prototype.setupForm = function() {
this.form.addClass('gfm-form');
// remove notify commit author checkbox for non-commit notes
gl.utils.disableButtonIfEmptyField(this.form.find('.js-note-text'), this.form.find('.js-comment-button, .js-note-new-discussion'));
new GfmAutoComplete(gl.GfmAutoComplete && gl.GfmAutoComplete.dataSources).setup(this.form.find('.js-gfm-input'));
new GfmAutoComplete(gl.GfmAutoComplete && gl.GfmAutoComplete.dataSources).setup(this.form.find('.js-gfm-input'), {
emojis: true,
members: this.enableGFM,
issues: this.enableGFM,
milestones: this.enableGFM,
mergeRequests: this.enableGFM,
labels: this.enableGFM,
});
new DropzoneInput(this.form);
autosize(this.textarea);
}
......
......@@ -44,18 +44,18 @@ export default class GroupName {
showToggle() {
this.title.classList.add('wrap');
this.toggle.classList.remove('hidden');
if (this.isHidden) this.groupTitle.classList.add('is-hidden');
if (this.isHidden) this.groupTitle.classList.add('hidden');
}
hideToggle() {
this.title.classList.remove('wrap');
this.toggle.classList.add('hidden');
if (this.isHidden) this.groupTitle.classList.remove('is-hidden');
if (this.isHidden) this.groupTitle.classList.remove('hidden');
}
toggleGroups() {
this.isHidden = !this.isHidden;
this.groupTitle.classList.toggle('is-hidden');
this.groupTitle.classList.toggle('hidden');
}
render() {
......
......@@ -3,7 +3,7 @@
prefer-arrow-callback, comma-dangle, consistent-return, yoda,
prefer-rest-params, prefer-spread, no-unused-vars, prefer-template,
promise/catch-or-return */
/* global Api */
import Api from './api';
var slice = [].slice;
......
/* eslint-disable func-names, space-before-function-paren, no-var, prefer-rest-params, wrap-iife, one-var, no-underscore-dangle, one-var-declaration-per-line, object-shorthand, no-unused-vars, no-new, comma-dangle, consistent-return, quotes, dot-notation, quote-props, prefer-arrow-callback, max-len */
/* global Flash */
import CreateMergeRequestDropdown from './create_merge_request_dropdown';
/* global Flash */
require('./flash');
require('~/lib/utils/text_utility');
require('vendor/jquery.waitforimages');
require('./task_list');
import 'vendor/jquery.waitforimages';
import '~/lib/utils/text_utility';
import './flash';
import './task_list';
import CreateMergeRequestDropdown from './create_merge_request_dropdown';
class Issue {
constructor() {
......
class AjaxCache {
import Cache from './cache';
class AjaxCache extends Cache {
constructor() {
this.internalStorage = { };
super();
this.pendingRequests = { };
}
get(endpoint) {
return this.internalStorage[endpoint];
}
hasData(endpoint) {
return Object.prototype.hasOwnProperty.call(this.internalStorage, endpoint);
}
remove(endpoint) {
delete this.internalStorage[endpoint];
}
retrieve(endpoint) {
if (this.hasData(endpoint)) {
return Promise.resolve(this.get(endpoint));
......
class Cache {
constructor() {
this.internalStorage = { };
}
get(key) {
return this.internalStorage[key];
}
hasData(key) {
return Object.prototype.hasOwnProperty.call(this.internalStorage, key);
}
remove(key) {
delete this.internalStorage[key];
}
}
export default Cache;
......@@ -198,10 +198,12 @@
const textBefore = value.substring(0, selectionStart);
const textAfter = value.substring(selectionEnd, value.length);
const newText = textBefore + text + textAfter;
const insertedText = text instanceof Function ? text(textBefore, textAfter) : text;
const newText = textBefore + insertedText + textAfter;
target.value = newText;
target.selectionStart = target.selectionEnd = selectionStart + text.length;
target.selectionStart = target.selectionEnd = selectionStart + insertedText.length;
// Trigger autosave
$(target).trigger('input');
......
/* eslint-disable func-names, space-before-function-paren, wrap-iife, no-var, no-param-reassign, no-cond-assign, comma-dangle, no-unused-expressions, prefer-template, max-len */
/* global timeago */
/* global dateFormat */
window.timeago = require('timeago.js');
window.dateFormat = require('vendor/date.format');
import timeago from 'timeago.js';
import dateFormat from 'vendor/date.format';
window.timeago = timeago;
window.dateFormat = dateFormat;
(function() {
(function(w) {
......@@ -101,8 +102,7 @@ window.dateFormat = require('vendor/date.format');
};
w.gl.utils.updateTimeagoText = function(el) {
const timeago = gl.utils.getTimeago();
const formattedDate = timeago.format(el.getAttribute('datetime'), 'gl_en');
const formattedDate = gl.utils.getTimeago().format(el.getAttribute('datetime'), 'gl_en');
if (el.textContent !== formattedDate) {
el.textContent = formattedDate;
......
......@@ -2,9 +2,7 @@
* exports HTTP status codes
*/
const statusCodes = {
export default {
NO_CONTENT: 204,
OK: 200,
};
module.exports = statusCodes;
/* 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, vars-on-top */
require('vendor/latinise');
import 'vendor/latinise';
var base;
var w = window;
......
import Api from '../../api';
import Cache from './cache';
class UsersCache extends Cache {
retrieve(username) {
if (this.hasData(username)) {
return Promise.resolve(this.get(username));
}
return Api.users('', { username })
.then((users) => {
if (!users.length) {
throw new Error(`User "${username}" could not be found!`);
}
if (users.length > 1) {
throw new Error(`Expected username "${username}" to be unique!`);
}
const user = users[0];
this.internalStorage[username] = user;
return user;
});
// missing catch is intentional, error handling depends on use case
}
}
export default new UsersCache();
......@@ -4,8 +4,6 @@
//
// Handles single- and multi-line selection and highlight for blob views.
//
require('vendor/jquery.scrollTo');
//
// ### Example Markup
//
......
This diff is collapsed.
......@@ -170,7 +170,7 @@ import './visibility_select';
import './wikis';
import './zen_mode';
// eslint-disable-next-line global-require
// eslint-disable-next-line global-require, import/no-commonjs
if (process.env.NODE_ENV !== 'production') require('./test_utils/');
document.addEventListener('beforeunload', function () {
......
......@@ -2,14 +2,13 @@
/* global Flash */
import Vue from 'vue';
require('./merge_conflict_store');
require('./merge_conflict_service');
require('./mixins/line_conflict_utils');
require('./mixins/line_conflict_actions');
require('./components/diff_file_editor');
require('./components/inline_conflict_lines');
require('./components/parallel_conflict_lines');
import './merge_conflict_store';
import './merge_conflict_service';
import './mixins/line_conflict_utils';
import './mixins/line_conflict_actions';
import './components/diff_file_editor';
import './components/inline_conflict_lines';
import './components/parallel_conflict_lines';
$(() => {
const INTERACTIVE_RESOLVE_MODE = 'interactive';
......
/* eslint-disable func-names, space-before-function-paren, no-var, prefer-rest-params, wrap-iife, quotes, no-underscore-dangle, one-var, one-var-declaration-per-line, consistent-return, dot-notation, quote-props, comma-dangle, object-shorthand, max-len, prefer-arrow-callback */
/* global MergeRequestTabs */
require('vendor/jquery.waitforimages');
require('./task_list');
require('./merge_request_tabs');
import 'vendor/jquery.waitforimages';
import './task_list';
import './merge_request_tabs';
(function() {
this.MergeRequest = (function() {
......
......@@ -288,7 +288,11 @@ import BlobForkSuggestion from './blob/blob_fork_suggestion';
if (anchor) {
const notesContent = anchor.closest('.notes_content');
const lineType = notesContent.hasClass('new') ? 'new' : 'old';
notes.addDiffNote(anchor, lineType, false);
notes.toggleDiffNote({
target: anchor,
lineType,
forceShow: true,
});
anchor[0].scrollIntoView();
// We have multiple elements on the page with `#note_xxx`
// (discussion and diff tabs) and `:target` only applies to the first
......
This diff is collapsed.
/* eslint-disable func-names, space-before-function-paren, no-var, prefer-rest-params, wrap-iife, one-var, vars-on-top, one-var-declaration-per-line, comma-dangle, object-shorthand, no-else-return, prefer-template, quotes, prefer-arrow-callback, no-param-reassign, no-cond-assign, max-len */
/* global Api */
import Api from './api';
(function() {
window.NamespaceSelect = (function() {
......
......@@ -6,15 +6,17 @@
import $ from 'jquery';
import Cookies from 'js-cookie';
import autosize from 'vendor/autosize';
import Dropzone from 'dropzone';
import 'vendor/jquery.caret'; // required by jquery.atwho
import 'vendor/jquery.atwho';
import CommentTypeToggle from './comment_type_toggle';
import './autosave';
import './dropzone_input';
import './task_list';
require('./autosave');
window.autosize = require('vendor/autosize');
window.Dropzone = require('dropzone');
require('./dropzone_input');
require('vendor/jquery.caret'); // required by jquery.atwho
require('vendor/jquery.atwho');
require('./task_list');
window.autosize = autosize;
window.Dropzone = Dropzone;
const normalizeNewlines = function(str) {
return str.replace(/\r\n/g, '\n');
......@@ -27,7 +29,7 @@ const normalizeNewlines = function(str) {
Notes.interval = null;
function Notes(notes_url, note_ids, last_fetched_at, view) {
function Notes(notes_url, note_ids, last_fetched_at, view, enableGFM = true) {
this.updateTargetButtons = this.updateTargetButtons.bind(this);
this.updateComment = this.updateComment.bind(this);
this.visibilityChange = this.visibilityChange.bind(this);
......@@ -50,6 +52,7 @@ const normalizeNewlines = function(str) {
this.notes_url = notes_url;
this.note_ids = note_ids;
this.enableGFM = enableGFM;
// Used to keep track of updated notes while people are editing things
this.updatedNotesTrackingMap = {};
this.last_fetched_at = last_fetched_at;
......@@ -286,6 +289,13 @@ const normalizeNewlines = function(str) {
}
};
Notes.prototype.setupNewNote = function($note) {
// Update datetime format on the recent note
gl.utils.localTimeAgo($note.find('.js-timeago'), false);
this.collapseLongCommitList();
this.taskList.init();
};
/*
Render note in main comments area.
......@@ -306,20 +316,17 @@ const normalizeNewlines = function(str) {
}
const $note = $notesList.find(`#note_${noteEntity.id}`);
if (this.isNewNote(noteEntity)) {
if (Notes.isNewNote(noteEntity, this.note_ids)) {
this.note_ids.push(noteEntity.id);
const $newNote = Notes.animateAppendNote(noteEntity.html, $notesList);
// Update datetime format on the recent note
gl.utils.localTimeAgo($newNote.find('.js-timeago'), false);
this.collapseLongCommitList();
this.taskList.init();
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 (this.isUpdatedNote(noteEntity, $note)) {
else if (Notes.isUpdatedNote(noteEntity, $note)) {
const isEditing = $note.hasClass('is-editing');
const initialContent = normalizeNewlines(
$note.find('.original-note-content').text().trim()
......@@ -340,30 +347,11 @@ const normalizeNewlines = function(str) {
}
else {
const $updatedNote = Notes.animateUpdateNote(noteEntity.html, $note);
// Update datetime format on the recent note
gl.utils.localTimeAgo($updatedNote.find('.js-timeago'), false);
this.setupNewNote($updatedNote);
}
}
};
/*
Check if note does not exists on page
*/
Notes.prototype.isNewNote = function(noteEntity) {
return $.inArray(noteEntity.id, this.note_ids) === -1;
};
Notes.prototype.isUpdatedNote = function(noteEntity, $note) {
// There can be CRLF vs LF mismatches if we don't sanitize and compare the same way
const sanitizedNoteNote = normalizeNewlines(noteEntity.note);
const currentNoteText = normalizeNewlines(
$note.find('.original-note-content').text().trim()
);
return sanitizedNoteNote !== currentNoteText;
};
Notes.prototype.isParallelView = function() {
return Cookies.get('diff_view') === 'parallel';
};
......@@ -376,7 +364,7 @@ const normalizeNewlines = function(str) {
Notes.prototype.renderDiscussionNote = function(noteEntity, $form) {
var discussionContainer, form, row, lineType, diffAvatarContainer;
if (!this.isNewNote(noteEntity)) {
if (!Notes.isNewNote(noteEntity, this.note_ids)) {
return;
}
this.note_ids.push(noteEntity.id);
......@@ -523,7 +511,7 @@ const normalizeNewlines = function(str) {
Notes.prototype.setupNoteForm = function(form) {
var textarea, key;
new gl.GLForm(form);
new gl.GLForm(form, this.enableGFM);
textarea = form.find(".js-note-text");
key = [
"Note",
......@@ -594,12 +582,12 @@ const normalizeNewlines = function(str) {
Updates the current note field.
*/
Notes.prototype.updateNote = function(_xhr, noteEntity, _status) {
Notes.prototype.updateNote = function(noteEntity, $targetNote) {
var $noteEntityEl, $note_li;
// Convert returned HTML to a jQuery object so we can modify it further
$noteEntityEl = $(noteEntity.html);
$noteEntityEl.addClass('fade-in-full');
this.revertNoteEditForm();
this.revertNoteEditForm($targetNote);
gl.utils.localTimeAgo($('.js-timeago', $noteEntityEl));
$noteEntityEl.renderGFM();
$noteEntityEl.find('.js-task-list-container').taskList('enable');
......@@ -681,10 +669,8 @@ const normalizeNewlines = function(str) {
if (this.updatedNotesTrackingMap[noteId]) {
const $newNote = $(this.updatedNotesTrackingMap[noteId].html);
$note.replaceWith($newNote);
this.setupNewNote($newNote);
this.updatedNotesTrackingMap[noteId] = null;
// Update datetime format on the recent note
gl.utils.localTimeAgo($newNote.find('.js-timeago'), false);
}
else {
$note.find('.js-finish-edit-warning').hide();
......@@ -874,12 +860,22 @@ const normalizeNewlines = function(str) {
Notes.prototype.onAddDiffNote = function(e) {
e.preventDefault();
const $link = $(e.currentTarget || e.target);
const link = e.currentTarget || e.target;
const $link = $(link);
const showReplyInput = !$link.hasClass('js-diff-comment-avatar');
this.addDiffNote($link, $link.data('lineType'), showReplyInput);
this.toggleDiffNote({
target: $link,
lineType: link.dataset.lineType,
showReplyInput
});
};
Notes.prototype.addDiffNote = function(target, lineType, showReplyInput) {
Notes.prototype.toggleDiffNote = function({
target,
lineType,
forceShow,
showReplyInput = false,
}) {
var $link, addForm, hasNotes, newForm, noteForm, replyButton, row, rowCssToAdd, targetContent, isDiffCommentAvatar;
$link = $(target);
row = $link.closest("tr");
......@@ -924,12 +920,12 @@ const normalizeNewlines = function(str) {
notesContent = targetRow.find(notesContentSelector);
addForm = true;
} else {
targetRow.show();
notesContent.toggle(!notesContent.is(':visible'));
const isCurrentlyShown = targetRow.find('.content:not(:empty)').is(':visible');
const isForced = forceShow === true || forceShow === false;
const showNow = forceShow === true || (!isCurrentlyShown && !isForced);
if (!targetRow.find('.content:not(:empty)').is(':visible')) {
targetRow.hide();
}
targetRow.toggle(showNow);
notesContent.toggle(showNow);
}
if (addForm) {
......@@ -1137,6 +1133,25 @@ const normalizeNewlines = function(str) {
return $form;
};
/**
* Check if note does not exists on page
*/
Notes.isNewNote = function(noteEntity, noteIds) {
return $.inArray(noteEntity.id, noteIds) === -1;
};
/**
* Check if $note already contains the `noteEntity` content
*/
Notes.isUpdatedNote = function(noteEntity, $note) {
// 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()
);
return sanitizedNoteEntityText !== currentNoteText;
};
Notes.checkMergeRequestStatus = function() {
if (gl.utils.getPagePath(1) === 'merge_requests') {
gl.mrWidget.checkStatus();
......@@ -1392,7 +1407,7 @@ const normalizeNewlines = function(str) {
gl.utils.ajaxPost(formAction, formData)
.then((note) => {
// Submission successful! render final note element
this.updateNote(null, note, null);
this.updateNote(note, $editingNote);
})
.fail(() => {
// Submission failed, revert back to original note
......
require('~/lib/utils/common_utils');
require('~/lib/utils/url_utility');
import '~/lib/utils/common_utils';
import '~/lib/utils/url_utility';
(() => {
const ENDLESS_SCROLL_BOTTOM_PX = 400;
......
import userAvatarLink from '../../vue_shared/components/user_avatar/user_avatar_link.vue';
export default {
props: [
'pipeline',
......@@ -7,6 +9,9 @@ export default {
return !!this.pipeline.user;
},
},
components: {
userAvatarLink,
},
template: `
<td>
<a
......@@ -15,18 +20,13 @@ export default {
<span class="pipeline-id">#{{pipeline.id}}</span>
</a>
<span>by</span>
<a
class="js-pipeline-url-user"
<user-avatar-link
v-if="user"
:href="pipeline.user.web_url">
<img
v-if="user"
class="avatar has-tooltip s20 "
:title="pipeline.user.name"
data-container="body"
:src="pipeline.user.avatar_url"
>
</a>
class="js-pipeline-url-user"
:link-href="pipeline.user.web_url"
:img-src="pipeline.user.avatar_url"
:tooltip-text="pipeline.user.name"
/>
<span
v-if="!user"
class="js-pipeline-url-api api">
......
require('./gl_crop');
require('./profile');
import './gl_crop';
import './profile';
export default function setupProjectEdit() {
const $transferForm = $('.js-project-transfer-form');
const $selectNamespace = $transferForm.find('.select2');
$selectNamespace.on('change', () => {
$transferForm.find(':submit').prop('disabled', !$selectNamespace.val());
});
$selectNamespace.trigger('change');
}
/* eslint-disable func-names, space-before-function-paren, wrap-iife, prefer-arrow-callback, no-var, comma-dangle, object-shorthand, one-var, one-var-declaration-per-line, no-else-return, quotes, max-len */
/* global Api */
import Api from './api';
(function() {
this.ProjectSelect = (function() {
......
require('./protected_branch_access_dropdown');
require('./protected_branch_create');
require('./protected_branch_dropdown');
require('./protected_branch_edit');
require('./protected_branch_edit_list');
import './protected_branch_access_dropdown';
import './protected_branch_create';
import './protected_branch_dropdown';
import './protected_branch_edit';
import './protected_branch_edit_list';
......@@ -6,6 +6,10 @@ const index = function index() {
currentUserId: gon.current_user_id,
whitelistUrls: [gon.gitlab_url],
isProduction: process.env.NODE_ENV,
release: gon.revision,
tags: {
revision: gon.revision,
},
});
return RavenConfig;
......
import Raven from 'raven-js';
import $ from 'jquery';
const IGNORE_ERRORS = [
// Random plugins/extensions
......@@ -57,6 +58,8 @@ const RavenConfig = {
configure() {
Raven.config(this.options.sentryDsn, {
release: this.options.release,
tags: this.options.tags,
whitelistUrls: this.options.whitelistUrls,
environment: this.options.isProduction ? 'production' : 'development',
ignoreErrors: this.IGNORE_ERRORS,
......@@ -72,7 +75,7 @@ const RavenConfig = {
},
bindRavenErrors() {
window.$(document).on('ajaxError.raven', this.handleRavenErrors);
$(document).on('ajaxError.raven', this.handleRavenErrors);
},
handleRavenErrors(event, req, config, err) {
......
/* eslint-disable func-names, space-before-function-paren, wrap-iife, no-var, one-var, one-var-declaration-per-line, object-shorthand, prefer-arrow-callback, comma-dangle, prefer-template, quotes, no-else-return, max-len */
/* global Flash */
/* global Api */
import Api from './api';
(function() {
this.Search = (function() {
......
/* global Mousetrap */
/* global Shortcuts */
require('./shortcuts');
import './shortcuts';
const defaults = {
skipResetBindings: false,
fileBlobPermalinkUrl: null,
};
class ShortcutsBlob extends Shortcuts {
export default class ShortcutsBlob extends Shortcuts {
constructor(opts) {
const options = Object.assign({}, defaults, opts);
super(options.skipResetBindings);
......@@ -25,5 +25,3 @@ class ShortcutsBlob extends Shortcuts {
}
}
}
module.exports = ShortcutsBlob;
......@@ -2,7 +2,7 @@
/* global Mousetrap */
/* global ShortcutsNavigation */
require('./shortcuts_navigation');
import './shortcuts_navigation';
(function() {
var extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; },
......
......@@ -3,8 +3,8 @@
/* global ShortcutsNavigation */
/* global sidebar */
require('mousetrap');
require('./shortcuts_navigation');
import 'mousetrap';
import './shortcuts_navigation';
(function() {
var extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; },
......@@ -38,7 +38,7 @@ require('./shortcuts_navigation');
}
ShortcutsIssuable.prototype.replyWithSelectedText = function() {
var quote, documentFragment, selected, separator;
var quote, documentFragment, el, selected, separator;
var replyField = $('.js-main-target-form #note_note');
documentFragment = window.gl.utils.getSelectedFragment();
......@@ -47,10 +47,8 @@ require('./shortcuts_navigation');
return;
}
// If the documentFragment contains more than just Markdown, don't copy as GFM.
if (documentFragment.querySelector('.md, .wiki')) return;
selected = window.gl.CopyAsGFM.nodeToGFM(documentFragment);
el = window.gl.CopyAsGFM.transformGFMSelection(documentFragment.cloneNode(true));
selected = window.gl.CopyAsGFM.nodeToGFM(el);
if (selected.trim() === "") {
return;
......
/* eslint-disable func-names, space-before-function-paren, max-len, no-var, one-var, no-restricted-syntax, vars-on-top, no-use-before-define, no-param-reassign, new-cap, no-underscore-dangle, wrap-iife, prefer-arrow-callback, consistent-return, no-return-assign */
/* global Mousetrap */
/* global Shortcuts */
import findAndFollowLink from './shortcuts_dashboard_navigation';
require('./shortcuts');
import findAndFollowLink from './shortcuts_dashboard_navigation';
import './shortcuts';
(function() {
var extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; },
......
......@@ -2,7 +2,7 @@
/* global Mousetrap */
/* global ShortcutsNavigation */
require('./shortcuts_navigation');
import './shortcuts_navigation';
(function() {
var extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; },
......
......@@ -69,10 +69,11 @@ export default {
<div>
<assignee-title
:number-of-assignees="store.assignees.length"
:loading="loading"
:loading="loading || store.isFetching.assignees"
:editable="store.editable"
/>
<assignees
v-if="!store.isFetching.assignees"
class="value"
:root-path="store.rootPath"
:users="store.assignees"
......
......@@ -10,6 +10,9 @@ export default class SidebarStore {
this.humanTimeEstimate = '';
this.humanTimeSpent = '';
this.assignees = [];
this.isFetching = {
assignees: true,
};
SidebarStore.singleton = this;
}
......@@ -18,6 +21,7 @@ export default class SidebarStore {
}
setAssigneeData(data) {
this.isFetching.assignees = false;
if (data.assignees) {
this.assignees = data.assignees;
}
......
......@@ -4,7 +4,7 @@
window.SingleFileDiff = (function() {
var COLLAPSED_HTML, ERROR_HTML, LOADING_HTML, WRAPPER;
WRAPPER = '<div class="diff-content diff-wrap-lines"></div>';
WRAPPER = '<div class="diff-content"></div>';
LOADING_HTML = '<i class="fa fa-spinner fa-spin"></i>';
......
/* global Flash */
require('vendor/task_list');
import 'vendor/task_list';
class TaskList {
constructor(options = {}) {
......
/* eslint-disable comma-dangle, max-len, no-useless-return, no-param-reassign, max-len */
/* global Api */
import Api from '../api';
import TemplateSelector from '../blob/template_selector';
......
require('vendor/xterm/encoding-indexes.js');
require('vendor/xterm/encoding.js');
window.Terminal = require('vendor/xterm/xterm.js');
require('vendor/xterm/fit.js');
require('./terminal.js');
import 'vendor/xterm/encoding-indexes';
import 'vendor/xterm/encoding';
import Terminal from 'vendor/xterm/xterm';
import 'vendor/xterm/fit';
import './terminal';
window.Terminal = Terminal;
$(() => new gl.Terminal({ selector: '#terminal' }));
......@@ -421,6 +421,15 @@ function UsersSelect(currentUser, els) {
selected = $dropdown.closest('.selectbox').find("input[name='" + ($dropdown.data('field-name')) + "']").val();
return assignTo(selected);
}
// Automatically close dropdown after assignee is selected
// since CE has no multiple assignees
// EE does not have a max-select
if ($dropdown.data('max-select') &&
getSelected().length === $dropdown.data('max-select')) {
// Close the dropdown
$dropdown.dropdown('toggle');
}
},
id: function (user) {
return user.id;
......
class VersionCheckImage {
export default class VersionCheckImage {
static bindErrorEvent(imageElement) {
imageElement.off('error').on('error', () => imageElement.hide());
}
......@@ -6,5 +6,3 @@ class VersionCheckImage {
window.gl = window.gl || {};
gl.VersionCheckImage = VersionCheckImage;
module.exports = VersionCheckImage;
......@@ -56,7 +56,7 @@ export default {
<div class="ci-widget">
<div class="ci-status-icon ci-status-icon-success">
<span class="js-icon-link icon-link">
<span
<span class="ci-status-icon"
v-html="svg"
aria-hidden="true"></span>
</span>
......
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
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