Skip to content
Snippets Groups Projects
Commit bb55e33f authored by Felipe Artur's avatar Felipe Artur
Browse files

Merge master into 26723-discussion-filters

parents bf801406 d87e88a6
No related branches found
No related tags found
No related merge requests found
Showing
with 283 additions and 69 deletions
Loading
Loading
@@ -5,6 +5,7 @@ import Icon from '~/vue_shared/components/icon.vue';
import ClipboardButton from '~/vue_shared/components/clipboard_button.vue';
import CIIcon from '~/vue_shared/components/ci_icon.vue';
import TimeAgoTooltip from '~/vue_shared/components/time_ago_tooltip.vue';
import CommitPipelineStatus from '~/projects/tree/components/commit_pipeline_status_component.vue';
 
/**
* CommitItem
Loading
Loading
@@ -29,6 +30,7 @@ export default {
ClipboardButton,
CIIcon,
TimeAgoTooltip,
CommitPipelineStatus,
},
props: {
commit: {
Loading
Loading
@@ -102,6 +104,14 @@ export default {
></pre>
</div>
<div class="commit-actions flex-row d-none d-sm-flex">
<div
v-if="commit.signatureHtml"
v-html="commit.signatureHtml"
></div>
<commit-pipeline-status
v-if="commit.pipelineStatusPath"
:endpoint="commit.pipelineStatusPath"
/>
<div class="commit-sha-group">
<div
class="label label-monospace"
Loading
Loading
Loading
Loading
@@ -115,7 +115,7 @@ export default {
<span>
{{ selectedVersionName }}
</span>
<Icon
<icon
:size="12"
name="angle-down"
class="position-absolute"
Loading
Loading
Loading
Loading
@@ -28,7 +28,7 @@ export default {
return diffModes[diffModeKey] || diffModes.replaced;
},
isTextFile() {
return this.diffFile.text;
return this.diffFile.viewer.name === 'text';
},
},
};
Loading
Loading
Loading
Loading
@@ -20,6 +20,11 @@ export default {
Tooltip,
},
props: {
discussionPath: {
type: String,
required: false,
default: '',
},
diffFile: {
type: Object,
required: true,
Loading
Loading
@@ -65,8 +70,7 @@ export default {
if (this.diffFile.submodule) {
return this.diffFile.submoduleTreeUrl || this.diffFile.submoduleLink;
}
return `#${this.diffFile.fileHash}`;
return this.discussionPath;
},
filePath() {
if (this.diffFile.submodule) {
Loading
Loading
@@ -152,7 +156,7 @@ export default {
v-once
ref="titleWrapper"
:href="titleLink"
class="append-right-4"
class="append-right-4 js-title-wrapper"
>
<file-icon
:file-name="filePath"
Loading
Loading
@@ -166,18 +170,16 @@ export default {
:title="diffFile.oldPath"
class="file-title-name"
data-container="body"
>
{{ diffFile.oldPath }}
</strong>
v-html="diffFile.oldPathHtml"
></strong>
<strong
v-tooltip
:title="diffFile.newPath"
class="file-title-name"
data-container="body"
>
{{ diffFile.newPath }}
</strong>
v-html="diffFile.newPathHtml"
></strong>
</span>
 
<strong
Loading
Loading
import Cookies from 'js-cookie';
import { getParameterValues } from '~/lib/utils/url_utility';
import bp from '~/breakpoints';
import { INLINE_DIFF_VIEW_TYPE, DIFF_VIEW_COOKIE_NAME, MR_TREE_SHOW_KEY } from '../../constants';
 
const viewTypeFromQueryString = getParameterValues('view')[0];
Loading
Loading
@@ -20,6 +21,7 @@ export default () => ({
diffViewType: viewTypeFromQueryString || viewTypeFromCookie || defaultViewType,
tree: [],
treeEntries: {},
showTreeList: storedTreeShow === null ? true : storedTreeShow === 'true',
showTreeList:
storedTreeShow === null ? bp.getBreakpointSize() !== 'xs' : storedTreeShow === 'true',
currentDiffFileId: '',
});
Loading
Loading
@@ -25,7 +25,7 @@ export const getReversePosition = linePosition => {
return LINE_POSITION_RIGHT;
};
 
export function getNoteFormData(params) {
export function getFormData(params) {
const {
note,
noteableType,
Loading
Loading
@@ -70,9 +70,15 @@ export function getNoteFormData(params) {
},
};
 
return postData;
}
export function getNoteFormData(params) {
const data = getFormData(params);
return {
endpoint: noteableData.create_note_path,
data: postData,
endpoint: params.noteableData.create_note_path,
data,
};
}
 
Loading
Loading
@@ -244,6 +250,7 @@ export function getDiffPositionByLineCode(diffFiles) {
oldLine,
newLine,
lineCode,
positionType: 'text',
};
}
});
Loading
Loading
@@ -259,8 +266,8 @@ export function isDiscussionApplicableToLine({ discussion, diffPosition, latestD
const { lineCode, ...diffPositionCopy } = diffPosition;
 
if (discussion.original_position && discussion.position) {
const originalRefs = convertObjectPropsToCamelCase(discussion.original_position.formatter);
const refs = convertObjectPropsToCamelCase(discussion.position.formatter);
const originalRefs = convertObjectPropsToCamelCase(discussion.original_position);
const refs = convertObjectPropsToCamelCase(discussion.position);
 
return _.isEqual(refs, diffPositionCopy) || _.isEqual(originalRefs, diffPositionCopy);
}
Loading
Loading
import DirtySubmitForm from './dirty_submit_form';
class DirtySubmitCollection {
constructor(forms) {
this.forms = forms;
this.dirtySubmits = [];
this.forms.forEach(form => this.dirtySubmits.push(new DirtySubmitForm(form)));
}
}
export default DirtySubmitCollection;
import DirtySubmitCollection from './dirty_submit_collection';
import DirtySubmitForm from './dirty_submit_form';
export default function dirtySubmitFactory(formOrForms) {
const isCollection = formOrForms instanceof NodeList || formOrForms instanceof Array;
const DirtySubmitClass = isCollection ? DirtySubmitCollection : DirtySubmitForm;
return new DirtySubmitClass(formOrForms);
}
import _ from 'underscore';
class DirtySubmitForm {
constructor(form) {
this.form = form;
this.dirtyInputs = [];
this.isDisabled = true;
this.init();
}
init() {
this.inputs = this.form.querySelectorAll('input, textarea, select');
this.submits = this.form.querySelectorAll('input[type=submit], button[type=submit]');
this.inputs.forEach(DirtySubmitForm.initInput);
this.toggleSubmission();
this.registerListeners();
}
registerListeners() {
const throttledUpdateDirtyInput = _.throttle(
event => this.updateDirtyInput(event),
DirtySubmitForm.THROTTLE_DURATION,
);
this.form.addEventListener('input', throttledUpdateDirtyInput);
this.form.addEventListener('submit', event => this.formSubmit(event));
}
updateDirtyInput(event) {
const input = event.target;
if (!input.dataset.dirtySubmitOriginalValue) return;
this.updateDirtyInputs(input);
this.toggleSubmission();
}
updateDirtyInputs(input) {
const { name } = input;
const isDirty =
input.dataset.dirtySubmitOriginalValue !== DirtySubmitForm.inputCurrentValue(input);
const indexOfInputName = this.dirtyInputs.indexOf(name);
const isExisting = indexOfInputName !== -1;
if (isDirty && !isExisting) this.dirtyInputs.push(name);
if (!isDirty && isExisting) this.dirtyInputs.splice(indexOfInputName, 1);
}
toggleSubmission() {
this.isDisabled = this.dirtyInputs.length === 0;
this.submits.forEach(element => {
element.disabled = this.isDisabled;
});
}
formSubmit(event) {
if (this.isDisabled) {
event.preventDefault();
event.stopImmediatePropagation();
}
return !this.isDisabled;
}
static initInput(element) {
element.dataset.dirtySubmitOriginalValue = DirtySubmitForm.inputCurrentValue(element);
}
static isInputCheckable(input) {
return input.type === 'checkbox' || input.type === 'radio';
}
static inputCurrentValue(input) {
return DirtySubmitForm.isInputCheckable(input) ? input.checked.toString() : input.value;
}
}
DirtySubmitForm.THROTTLE_DURATION = 500;
export default DirtySubmitForm;
Loading
Loading
@@ -5,6 +5,7 @@ import { __ } from '~/locale';
import axios from './lib/utils/axios_utils';
import { timeFor } from './lib/utils/datetime_utility';
import { parsePikadayDate, pikadayToString } from './lib/utils/datefix';
import boardsStore from './boards/stores/boards_store';
 
class DueDateSelect {
constructor({ $dropdown, $loading } = {}) {
Loading
Loading
@@ -58,7 +59,7 @@ class DueDateSelect {
$dueDateInput.val(calendar.toString(dateText));
 
if (this.$dropdown.hasClass('js-issue-boards-due-date')) {
gl.issueBoards.BoardsStore.detail.issue.dueDate = $dueDateInput.val();
boardsStore.detail.issue.dueDate = $dueDateInput.val();
this.updateIssueBoardIssue();
} else {
this.saveDueDate(true);
Loading
Loading
@@ -79,7 +80,7 @@ class DueDateSelect {
calendar.setDate(null);
 
if (this.$dropdown.hasClass('js-issue-boards-due-date')) {
gl.issueBoards.BoardsStore.detail.issue.dueDate = '';
boardsStore.detail.issue.dueDate = '';
this.updateIssueBoardIssue();
} else {
$(`input[name='${this.fieldName}']`).val('');
Loading
Loading
@@ -123,7 +124,7 @@ class DueDateSelect {
this.$loading.fadeOut();
};
 
gl.issueBoards.BoardsStore.detail.issue
boardsStore.detail.issue
.update(this.$dropdown.attr('data-issue-update'))
.then(fadeOutLoader)
.catch(fadeOutLoader);
Loading
Loading
Loading
Loading
@@ -60,11 +60,9 @@ export default {
>
<span>
<icon name="play" />
<i
class="fa fa-caret-down"
aria-hidden="true"
>
</i>
<icon
name="chevron-down"
/>
<gl-loading-icon v-if="isLoading" />
</span>
</button>
Loading
Loading
Loading
Loading
@@ -4,6 +4,7 @@ import _ from 'underscore';
import tooltip from '~/vue_shared/directives/tooltip';
import UserAvatarLink from '~/vue_shared/components/user_avatar/user_avatar_link.vue';
import { humanize } from '~/lib/utils/text_utility';
import Icon from '~/vue_shared/components/icon.vue';
import ActionsComponent from './environment_actions.vue';
import ExternalUrlComponent from './environment_external_url.vue';
import StopComponent from './environment_stop.vue';
Loading
Loading
@@ -24,6 +25,7 @@ export default {
components: {
UserAvatarLink,
CommitComponent,
Icon,
ActionsComponent,
ExternalUrlComponent,
StopComponent,
Loading
Loading
@@ -448,6 +450,10 @@ export default {
this.canRetry
);
},
folderIconName() {
return this.model.isOpen ? 'chevron-down' : 'chevron-right';
},
},
 
methods: {
Loading
Loading
@@ -466,7 +472,9 @@ export default {
class="gl-responsive-table-row"
role="row">
<div
class="table-section section-wrap section-15"
v-tooltip
:title="model.name"
class="table-section section-wrap section-15 text-truncate"
role="gridcell"
>
<div
Loading
Loading
@@ -480,9 +488,8 @@ export default {
v-if="!model.isFolder"
class="environment-name table-mobile-content">
<a
v-tooltip
class="qa-environment-link"
:href="environmentPath"
:title="model.name"
>
{{ model.name }}
</a>
Loading
Loading
@@ -493,27 +500,15 @@ export default {
role="button"
@click="onClickFolder">
 
<span class="folder-icon">
<i
v-show="model.isOpen"
class="fa fa-caret-down"
aria-hidden="true"
>
</i>
<i
v-show="!model.isOpen"
class="fa fa-caret-right"
aria-hidden="true"
>
</i>
</span>
<icon
:name="folderIconName"
class="folder-icon"
/>
 
<span class="folder-icon">
<i
class="fa fa-folder"
aria-hidden="true">
</i>
</span>
<icon
name="folder"
class="folder-icon"
/>
 
<span>
{{ model.folderName }}
Loading
Loading
<script>
import _ from 'underscore';
import { mapActions } from 'vuex';
import Icon from '~/vue_shared/components/icon.vue';
import eventHub from '../event_hub';
import frequentItemsMixin from './frequent_items_mixin';
 
export default {
components: {
Icon,
},
mixins: [frequentItemsMixin],
data() {
return {
Loading
Loading
@@ -45,11 +49,10 @@ export default {
type="search"
class="form-control"
/>
<i
<icon
v-if="!searchQuery"
class="search-icon fa fa-fw fa-search"
aria-hidden="true"
>
</i>
name="search"
class="search-icon"
/>
</div>
</template>
Loading
Loading
@@ -15,6 +15,7 @@ export const defaultAutocompleteConfig = {
epics: true,
milestones: true,
labels: true,
snippets: true,
};
 
class GfmAutoComplete {
Loading
Loading
@@ -50,6 +51,7 @@ class GfmAutoComplete {
if (this.enableMap.milestones) this.setupMilestones($input);
if (this.enableMap.mergeRequests) this.setupMergeRequests($input);
if (this.enableMap.labels) this.setupLabels($input);
if (this.enableMap.snippets) this.setupSnippets($input);
 
// We don't instantiate the quick actions autocomplete for note and issue/MR edit forms
$input.filter('[data-supports-quick-actions="true"]').atwho({
Loading
Loading
@@ -360,6 +362,39 @@ class GfmAutoComplete {
});
}
 
setupSnippets($input) {
$input.atwho({
at: '$',
alias: 'snippets',
searchKey: 'search',
displayTpl(value) {
let tmpl = GfmAutoComplete.Loading.template;
if (value.title != null) {
tmpl = GfmAutoComplete.Issues.template;
}
return tmpl;
},
data: GfmAutoComplete.defaultLoadingData,
// eslint-disable-next-line no-template-curly-in-string
insertTpl: '${atwho-at}${id}',
callbacks: {
...this.getDefaultCallbacks(),
beforeSave(snippets) {
return $.map(snippets, (m) => {
if (m.title == null) {
return m;
}
return {
id: m.id,
title: sanitize(m.title),
search: `${m.id} ${m.title}`,
};
});
},
},
});
}
getDefaultCallbacks() {
const fetchData = this.fetchData.bind(this);
 
Loading
Loading
@@ -470,7 +505,7 @@ class GfmAutoComplete {
// The below is taken from At.js source
// Tweaked to commands to start without a space only if char before is a non-word character
// https://github.com/ichord/At.js
const atSymbolsWithBar = Object.keys(controllers).join('|');
const atSymbolsWithBar = Object.keys(controllers).join('|').replace(/[$]/, '\\$&');
const atSymbolsWithoutBar = Object.keys(controllers).join('');
const targetSubtext = subtext.split(GfmAutoComplete.regexSubtext).pop();
const resultantFlag = flag.replace(/[-[\]/{}()*+?.\\^$|]/g, '\\$&');
Loading
Loading
@@ -497,6 +532,7 @@ GfmAutoComplete.atTypeMap = {
'~': 'labels',
'%': 'milestones',
'/': 'commands',
$: 'snippets',
};
 
// Emoji
Loading
Loading
@@ -519,7 +555,7 @@ GfmAutoComplete.Labels = {
// eslint-disable-next-line no-template-curly-in-string
template: '<li><span class="dropdown-label-box" style="background: ${color}"></span> ${title}</li>',
};
// Issues and MergeRequests
// Issues, MergeRequests and Snippets
GfmAutoComplete.Issues = {
// eslint-disable-next-line no-template-curly-in-string
template: '<li><small>${id}</small> ${title}</li>',
Loading
Loading
/* eslint-disable func-names, no-underscore-dangle, no-var, one-var, one-var-declaration-per-line, max-len, vars-on-top, wrap-iife, no-unused-vars, no-shadow, no-cond-assign, prefer-arrow-callback, no-return-assign, no-else-return, camelcase, no-lonely-if, guard-for-in, no-restricted-syntax, consistent-return, prefer-template, no-param-reassign, no-loop-func */
/* eslint-disable func-names, no-underscore-dangle, no-var, one-var, vars-on-top, no-unused-vars, no-shadow, no-cond-assign, prefer-arrow-callback, no-return-assign, no-else-return, camelcase, no-lonely-if, guard-for-in, no-restricted-syntax, consistent-return, prefer-template, no-param-reassign, no-loop-func */
/* global fuzzaldrinPlus */
 
import $ from 'jquery';
Loading
Loading
import $ from 'jquery';
import Vue from 'vue';
import Translate from '~/vue_shared/translate';
import { highCountTrim } from '~/lib/utils/text_utility';
import SetStatusModalTrigger from './set_status_modal/set_status_modal_trigger.vue';
import SetStatusModalWrapper from './set_status_modal/set_status_modal_wrapper.vue';
 
/**
* Updates todo counter when todos are toggled.
Loading
Loading
@@ -17,3 +21,54 @@ export default function initTodoToggle() {
$todoPendingCount.toggleClass('hidden', parsedCount === 0);
});
}
document.addEventListener('DOMContentLoaded', () => {
const setStatusModalTriggerEl = document.querySelector('.js-set-status-modal-trigger');
const setStatusModalWrapperEl = document.querySelector('.js-set-status-modal-wrapper');
if (setStatusModalTriggerEl || setStatusModalWrapperEl) {
Vue.use(Translate);
// eslint-disable-next-line no-new
new Vue({
el: setStatusModalTriggerEl,
data() {
const { hasStatus } = this.$options.el.dataset;
return {
hasStatus: hasStatus === 'true',
};
},
render(createElement) {
return createElement(SetStatusModalTrigger, {
props: {
hasStatus: this.hasStatus,
},
});
},
});
// eslint-disable-next-line no-new
new Vue({
el: setStatusModalWrapperEl,
data() {
const { currentEmoji, currentMessage } = this.$options.el.dataset;
return {
currentEmoji,
currentMessage,
};
},
render(createElement) {
const { currentEmoji, currentMessage } = this;
return createElement(SetStatusModalWrapper, {
props: {
currentEmoji,
currentMessage,
},
});
},
});
}
});
Loading
Loading
@@ -117,7 +117,7 @@ export default {
<button
:disabled="!hasChanges"
type="button"
class="btn btn-primary btn-sm btn-block"
class="btn btn-primary btn-sm btn-block qa-begin-commit-button"
@click="toggleIsSmall"
>
{{ __('Commit…') }}
Loading
Loading
@@ -147,7 +147,7 @@ export default {
<loading-button
:loading="submitCommitLoading"
:label="commitButtonText"
container-class="btn btn-success btn-sm float-left"
container-class="btn btn-success btn-sm float-left qa-commit-button"
@click="commitChanges"
/>
<button
Loading
Loading
Loading
Loading
@@ -38,14 +38,18 @@ export default {
return this.modifiedFilesLength ? 'multi-file-modified' : '';
},
additionsTooltip() {
return sprintf(n__('1 %{type} addition', '%{count} %{type} additions', this.addedFilesLength), {
type: this.title.toLowerCase(),
count: this.addedFilesLength,
});
return sprintf(
n__('1 %{type} addition', '%{count} %{type} additions', this.addedFilesLength),
{
type: this.title.toLowerCase(),
count: this.addedFilesLength,
},
);
},
modifiedTooltip() {
return sprintf(
n__('1 %{type} modification', '%{count} %{type} modifications', this.modifiedFilesLength), {
n__('1 %{type} modification', '%{count} %{type} modifications', this.modifiedFilesLength),
{
type: this.title.toLowerCase(),
count: this.modifiedFilesLength,
},
Loading
Loading
Loading
Loading
@@ -25,10 +25,7 @@ export default {
return `discard-file-${this.path}`;
},
modalTitle() {
return sprintf(
__('Discard changes to %{path}?'),
{ path: this.path },
);
return sprintf(__('Discard changes to %{path}?'), { path: this.path });
},
},
methods: {
Loading
Loading
Loading
Loading
@@ -47,7 +47,7 @@ export default {
</script>
 
<template>
<div class="d-flex align-items-center ide-file-templates">
<div class="d-flex align-items-center ide-file-templates qa-file-templates-bar">
<strong class="append-right-default">
{{ __('File templates') }}
</strong>
Loading
Loading
@@ -63,7 +63,7 @@ export default {
:is-async-data="true"
:searchable="true"
:title="__('File templates')"
class="mr-2"
class="mr-2 qa-file-template-dropdown"
@click="selectTemplate"
/>
<transition name="fade">
Loading
Loading
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment