Skip to content
Snippets Groups Projects
Commit dba86447 authored by GitLab Bot's avatar GitLab Bot
Browse files

Add latest changes from gitlab-org/gitlab@12-8-stable-ee

parent b7d29500
No related branches found
No related tags found
No related merge requests found
Showing
with 541 additions and 84 deletions
import Mousetrap from 'mousetrap';
import { getLocationHash, visitUrl } from '../../lib/utils/url_utility';
import {
getLocationHash,
updateHistory,
urlIsDifferent,
urlContainsSha,
getShaFromUrl,
} from '~/lib/utils/url_utility';
import { updateRefPortionOfTitle } from '~/repository/utils/title';
import Shortcuts from './shortcuts';
 
const defaults = {
skipResetBindings: false,
fileBlobPermalinkUrl: null,
fileBlobPermalinkUrlElement: null,
};
 
function eventHasModifierKeys(event) {
// We ignore alt because I don't think alt clicks normally do anything special?
return event.ctrlKey || event.metaKey || event.shiftKey;
}
export default class ShortcutsBlob extends Shortcuts {
constructor(opts) {
const options = Object.assign({}, defaults, opts);
super(options.skipResetBindings);
this.options = options;
 
this.shortcircuitPermalinkButton();
Mousetrap.bind('y', this.moveToFilePermalink.bind(this));
}
 
moveToFilePermalink() {
if (this.options.fileBlobPermalinkUrl) {
const permalink = this.options.fileBlobPermalinkUrl;
if (permalink) {
const hash = getLocationHash();
const hashUrlString = hash ? `#${hash}` : '';
visitUrl(`${this.options.fileBlobPermalinkUrl}${hashUrlString}`);
if (urlIsDifferent(permalink)) {
updateHistory({
url: `${permalink}${hashUrlString}`,
title: document.title,
});
}
if (urlContainsSha({ url: permalink })) {
updateRefPortionOfTitle(getShaFromUrl({ url: permalink }));
}
}
}
shortcircuitPermalinkButton() {
const button = this.options.fileBlobPermalinkUrlElement;
const handleButton = e => {
if (!eventHasModifierKeys(e)) {
e.preventDefault();
this.moveToFilePermalink();
}
};
if (button) {
button.addEventListener('click', handleButton);
}
}
}
import Mousetrap from 'mousetrap';
import 'mousetrap/plugins/pause/mousetrap-pause';
const shorcutsDisabledKey = 'shortcutsDisabled';
export const shouldDisableShortcuts = () => {
try {
return localStorage.getItem(shorcutsDisabledKey) === 'true';
} catch (e) {
return false;
}
};
export function enableShortcuts() {
localStorage.setItem(shorcutsDisabledKey, false);
Mousetrap.unpause();
}
export function disableShortcuts() {
localStorage.setItem(shorcutsDisabledKey, true);
Mousetrap.pause();
}
<script>
import { GlToggle, GlSprintf } from '@gitlab/ui';
import AccessorUtilities from '~/lib/utils/accessor';
import { disableShortcuts, enableShortcuts, shouldDisableShortcuts } from './shortcuts_toggle';
export default {
components: {
GlSprintf,
GlToggle,
},
data() {
return {
localStorageUsable: AccessorUtilities.isLocalStorageAccessSafe(),
shortcutsEnabled: !shouldDisableShortcuts(),
};
},
methods: {
onChange(value) {
this.shortcutsEnabled = value;
if (value) {
enableShortcuts();
} else {
disableShortcuts();
}
},
},
};
</script>
<template>
<div v-if="localStorageUsable" class="d-inline-flex align-items-center js-toggle-shortcuts">
<gl-toggle
v-model="shortcutsEnabled"
aria-describedby="shortcutsToggle"
class="prepend-left-10 mb-0"
label-position="right"
@change="onChange"
>
<template #labelOn>
<gl-sprintf
:message="__('%{screenreaderOnlyStart}Keyboard shorcuts%{screenreaderOnlyEnd} Enabled')"
>
<template #screenreaderOnly="{ content }">
<span class="sr-only">{{ content }}</span>
</template>
</gl-sprintf>
</template>
<template #labelOff>
<gl-sprintf
:message="__('%{screenreaderOnlyStart}Keyboard shorcuts%{screenreaderOnlyEnd} Disabled')"
>
<template #screenreaderOnly="{ content }">
<span class="sr-only">{{ content }}</span>
</template>
</gl-sprintf>
</template>
</gl-toggle>
<div id="shortcutsToggle" class="sr-only">{{ __('Enable or disable keyboard shortcuts') }}</div>
</div>
</template>
<script>
import { GlLoadingIcon } from '@gitlab/ui';
import { RichViewer, SimpleViewer } from '~/vue_shared/components/blob_viewers';
import BlobContentError from './blob_content_error.vue';
export default {
components: {
GlLoadingIcon,
BlobContentError,
},
props: {
content: {
type: String,
default: '',
required: false,
},
loading: {
type: Boolean,
default: true,
required: false,
},
activeViewer: {
type: Object,
required: true,
},
},
computed: {
viewer() {
switch (this.activeViewer.type) {
case 'rich':
return RichViewer;
default:
return SimpleViewer;
}
},
viewerError() {
return this.activeViewer.renderError;
},
},
};
</script>
<template>
<div class="blob-viewer" :data-type="activeViewer.type">
<gl-loading-icon v-if="loading" size="md" color="dark" class="my-4 mx-auto" />
<template v-else>
<blob-content-error v-if="viewerError" :viewer-error="viewerError" />
<component :is="viewer" v-else ref="contentViewer" :content="content" />
</template>
</div>
</template>
<script>
export default {
props: {
viewerError: {
type: String,
required: true,
},
},
};
</script>
<template>
<div class="file-content code">
<div class="text-center py-4" v-html="viewerError"></div>
</div>
</template>
<script>
import { GlFormInputGroup, GlButton, GlIcon } from '@gitlab/ui';
import { __ } from '~/locale';
export default {
components: {
GlFormInputGroup,
GlButton,
GlIcon,
},
props: {
url: {
type: String,
required: true,
},
},
data() {
return {
optionValues: [
// eslint-disable-next-line no-useless-escape
{ name: __('Embed'), value: `<script src='${this.url}.js'><\/script>` },
{ name: __('Share'), value: this.url },
],
};
},
};
</script>
<template>
<gl-form-input-group
id="embeddable-text"
:predefined-options="optionValues"
readonly
select-on-click
>
<template #append>
<gl-button new-style data-clipboard-target="#embeddable-text">
<gl-icon name="copy-to-clipboard" :title="__('Copy')" />
</gl-button>
</template>
</gl-form-input-group>
</template>
<script>
import ViewerSwitcher from './blob_header_viewer_switcher.vue';
import DefaultActions from './blob_header_default_actions.vue';
import BlobFilepath from './blob_header_filepath.vue';
import { SIMPLE_BLOB_VIEWER } from './constants';
export default {
components: {
ViewerSwitcher,
DefaultActions,
BlobFilepath,
},
props: {
blob: {
type: Object,
required: true,
},
hideDefaultActions: {
type: Boolean,
required: false,
default: false,
},
hideViewerSwitcher: {
type: Boolean,
required: false,
default: false,
},
activeViewerType: {
type: String,
required: false,
default: SIMPLE_BLOB_VIEWER,
},
},
data() {
return {
viewer: this.hideViewerSwitcher ? null : this.activeViewerType,
};
},
computed: {
showViewerSwitcher() {
return !this.hideViewerSwitcher && Boolean(this.blob.simpleViewer && this.blob.richViewer);
},
showDefaultActions() {
return !this.hideDefaultActions;
},
},
watch: {
viewer(newVal, oldVal) {
if (!this.hideViewerSwitcher && newVal !== oldVal) {
this.$emit('viewer-changed', newVal);
}
},
},
methods: {
proxyCopyRequest() {
this.$emit('copy');
},
},
};
</script>
<template>
<div class="js-file-title file-title-flex-parent">
<blob-filepath :blob="blob">
<template #filepathPrepend>
<slot name="prepend"></slot>
</template>
</blob-filepath>
<div class="file-actions d-none d-sm-block">
<viewer-switcher v-if="showViewerSwitcher" v-model="viewer" />
<slot name="actions"></slot>
<default-actions
v-if="showDefaultActions"
:raw-path="blob.rawPath"
:active-viewer="viewer"
@copy="proxyCopyRequest"
/>
</div>
</div>
</template>
<script>
import { GlButton, GlButtonGroup, GlIcon, GlTooltipDirective } from '@gitlab/ui';
import {
BTN_COPY_CONTENTS_TITLE,
BTN_DOWNLOAD_TITLE,
BTN_RAW_TITLE,
RICH_BLOB_VIEWER,
SIMPLE_BLOB_VIEWER,
} from './constants';
export default {
components: {
GlIcon,
GlButtonGroup,
GlButton,
},
directives: {
GlTooltip: GlTooltipDirective,
},
props: {
rawPath: {
type: String,
required: true,
},
activeViewer: {
type: String,
default: SIMPLE_BLOB_VIEWER,
required: false,
},
},
computed: {
downloadUrl() {
return `${this.rawPath}?inline=false`;
},
copyDisabled() {
return this.activeViewer === RICH_BLOB_VIEWER;
},
},
BTN_COPY_CONTENTS_TITLE,
BTN_DOWNLOAD_TITLE,
BTN_RAW_TITLE,
};
</script>
<template>
<gl-button-group>
<gl-button
v-gl-tooltip.hover
:aria-label="$options.BTN_COPY_CONTENTS_TITLE"
:title="$options.BTN_COPY_CONTENTS_TITLE"
:disabled="copyDisabled"
data-clipboard-target="#blob-code-content"
>
<gl-icon name="copy-to-clipboard" :size="14" />
</gl-button>
<gl-button
v-gl-tooltip.hover
:aria-label="$options.BTN_RAW_TITLE"
:title="$options.BTN_RAW_TITLE"
:href="rawPath"
target="_blank"
>
<gl-icon name="doc-code" :size="14" />
</gl-button>
<gl-button
v-gl-tooltip.hover
:aria-label="$options.BTN_DOWNLOAD_TITLE"
:title="$options.BTN_DOWNLOAD_TITLE"
:href="downloadUrl"
target="_blank"
>
<gl-icon name="download" :size="14" />
</gl-button>
</gl-button-group>
</template>
<script>
import FileIcon from '~/vue_shared/components/file_icon.vue';
import ClipboardButton from '~/vue_shared/components/clipboard_button.vue';
import { numberToHumanSize } from '~/lib/utils/number_utils';
export default {
components: {
FileIcon,
ClipboardButton,
},
props: {
blob: {
type: Object,
required: true,
},
},
computed: {
blobSize() {
return numberToHumanSize(this.blob.size);
},
gfmCopyText() {
return `\`${this.blob.path}\``;
},
},
};
</script>
<template>
<div class="file-header-content d-flex align-items-center lh-100">
<slot name="filepathPrepend"></slot>
<file-icon :file-name="blob.path" :size="18" aria-hidden="true" css-classes="mr-2" />
<strong
v-if="blob.name"
class="file-title-name qa-file-title-name mr-1 js-blob-header-filepath"
>{{ blob.name }}</strong
>
<small class="mr-2">{{ blobSize }}</small>
<clipboard-button
:text="blob.path"
:gfm="gfmCopyText"
:title="__('Copy file path')"
css-class="btn-clipboard btn-transparent lh-100 position-static"
/>
</div>
</template>
<script>
import { GlButton, GlButtonGroup, GlIcon, GlTooltipDirective } from '@gitlab/ui';
import {
RICH_BLOB_VIEWER,
RICH_BLOB_VIEWER_TITLE,
SIMPLE_BLOB_VIEWER,
SIMPLE_BLOB_VIEWER_TITLE,
} from './constants';
export default {
components: {
GlIcon,
GlButtonGroup,
GlButton,
},
directives: {
GlTooltip: GlTooltipDirective,
},
props: {
value: {
type: String,
default: SIMPLE_BLOB_VIEWER,
required: false,
},
},
computed: {
isSimpleViewer() {
return this.value === SIMPLE_BLOB_VIEWER;
},
isRichViewer() {
return this.value === RICH_BLOB_VIEWER;
},
},
methods: {
switchToViewer(viewer) {
if (viewer !== this.value) {
this.$emit('input', viewer);
}
},
},
SIMPLE_BLOB_VIEWER,
RICH_BLOB_VIEWER,
SIMPLE_BLOB_VIEWER_TITLE,
RICH_BLOB_VIEWER_TITLE,
};
</script>
<template>
<gl-button-group class="js-blob-viewer-switcher ml-2">
<gl-button
v-gl-tooltip.hover
:aria-label="$options.SIMPLE_BLOB_VIEWER_TITLE"
:title="$options.SIMPLE_BLOB_VIEWER_TITLE"
:selected="isSimpleViewer"
:class="{ active: isSimpleViewer }"
@click="switchToViewer($options.SIMPLE_BLOB_VIEWER)"
>
<gl-icon name="code" :size="14" />
</gl-button>
<gl-button
v-gl-tooltip.hover
:aria-label="$options.RICH_BLOB_VIEWER_TITLE"
:title="$options.RICH_BLOB_VIEWER_TITLE"
:selected="isRichViewer"
:class="{ active: isRichViewer }"
@click="switchToViewer($options.RICH_BLOB_VIEWER)"
>
<gl-icon name="document" :size="14" />
</gl-button>
</gl-button-group>
</template>
import { __ } from '~/locale';
export const BTN_COPY_CONTENTS_TITLE = __('Copy file contents');
export const BTN_RAW_TITLE = __('Open raw');
export const BTN_DOWNLOAD_TITLE = __('Download');
export const SIMPLE_BLOB_VIEWER = 'simple';
export const SIMPLE_BLOB_VIEWER_TITLE = __('Display source');
export const RICH_BLOB_VIEWER = 'rich';
export const RICH_BLOB_VIEWER_TITLE = __('Display rendered file');
Loading
Loading
@@ -117,11 +117,7 @@ export default class FileTemplateMediator {
selector.hide();
}
});
if (this.editor.getValue() !== '') {
this.setTypeSelectorToggleText(item.name);
}
this.setTypeSelectorToggleText(item.name);
this.cacheToggleText();
}
 
Loading
Loading
Loading
Loading
@@ -75,10 +75,10 @@ export default () => {
class="text-center"
v-if="error">
<span v-if="loadError">
An error occurred whilst loading the file. Please try again later.
An error occurred while loading the file. Please try again later.
</span>
<span v-else>
An error occurred whilst parsing the file.
An error occurred while parsing the file.
</span>
</p>
</div>
Loading
Loading
import Vue from 'vue';
import pdfLab from '../../pdf/index.vue';
import { GlLoadingIcon } from '@gitlab/ui';
 
export default () => {
const el = document.getElementById('js-pdf-viewer');
Loading
Loading
@@ -8,6 +9,7 @@ export default () => {
el,
components: {
pdfLab,
GlLoadingIcon,
},
data() {
return {
Loading
Loading
@@ -32,11 +34,7 @@ export default () => {
<div
class="text-center loading"
v-if="loading && !error">
<i
class="fa fa-spinner fa-spin"
aria-hidden="true"
aria-label="PDF loading">
</i>
<gl-loading-icon class="mt-5" size="lg"/>
</div>
<pdf-lab
v-if="!loadError"
Loading
Loading
@@ -47,10 +45,10 @@ export default () => {
class="text-center"
v-if="error">
<span v-if="loadError">
An error occurred whilst loading the file. Please try again later.
An error occurred while loading the file. Please try again later.
</span>
<span v-else>
An error occurred whilst decoding the file.
An error occurred while decoding the file.
</span>
</p>
</div>
Loading
Loading
Loading
Loading
@@ -181,6 +181,8 @@ export default {
 
boardsStore.startMoving(list, issue);
 
this.$root.$emit('bv::hide::tooltip');
sortableStart();
},
onAdd: e => {
Loading
Loading
Loading
Loading
@@ -161,6 +161,14 @@ export default {
<div>
<div class="d-flex board-card-header" dir="auto">
<h4 class="board-card-title append-bottom-0 prepend-top-0">
<icon
v-if="issue.blocked"
v-gl-tooltip
name="issue-block"
:title="__('Blocked issue')"
class="issue-blocked-icon append-right-4"
:aria-label="__('Blocked issue')"
/>
<icon
v-if="issue.confidential"
v-gl-tooltip
Loading
Loading
@@ -233,7 +241,7 @@ export default {
:key="assignee.id"
:link-href="assigneeUrl(assignee)"
:img-alt="avatarUrlTitle(assignee)"
:img-src="assignee.avatar"
:img-src="assignee.avatar || assignee.avatar_url"
:img-size="24"
class="js-no-trigger"
tooltip-placement="bottom"
Loading
Loading
Loading
Loading
@@ -25,7 +25,7 @@ export default {
</script>
 
<template>
<div class="issue-count">
<div class="issue-count text-nowrap">
<span class="js-issue-size" :class="{ 'text-danger': issuesExceedMax }">
{{ issuesSize }}
</span>
Loading
Loading
Loading
Loading
@@ -26,6 +26,7 @@ export function getBoardSortableDefaultOptions(obj) {
scrollSpeed: 20,
onStart: sortableStart,
onEnd: sortableEnd,
fallbackTolerance: 1,
});
 
Object.keys(obj).forEach(key => {
Loading
Loading
Loading
Loading
@@ -37,6 +37,7 @@ class ListIssue {
this.project_id = obj.project_id;
this.timeEstimate = obj.time_estimate;
this.assignableLabelsEndpoint = obj.assignable_labels_endpoint;
this.blocked = obj.blocked;
 
if (obj.project) {
this.project = new IssueProject(obj.project);
Loading
Loading
Loading
Loading
@@ -83,27 +83,7 @@ class List {
}
 
save() {
const entity = this.label || this.assignee || this.milestone;
let entityType = '';
if (this.label) {
entityType = 'label_id';
} else if (this.assignee) {
entityType = 'assignee_id';
} else if (IS_EE && this.milestone) {
entityType = 'milestone_id';
}
return boardsStore
.createList(entity.id, entityType)
.then(res => res.data)
.then(data => {
this.id = data.id;
this.type = data.list_type;
this.position = data.position;
this.label = data.label;
return this.getIssues();
});
return boardsStore.saveList(this);
}
 
destroy() {
Loading
Loading
@@ -181,50 +161,7 @@ class List {
}
 
addMultipleIssues(issues, listFrom, newIndex) {
let moveBeforeId = null;
let moveAfterId = null;
const listHasIssues = issues.every(issue => this.findIssue(issue.id));
if (!listHasIssues) {
if (newIndex !== undefined) {
if (this.issues[newIndex - 1]) {
moveBeforeId = this.issues[newIndex - 1].id;
}
if (this.issues[newIndex]) {
moveAfterId = this.issues[newIndex].id;
}
this.issues.splice(newIndex, 0, ...issues);
} else {
this.issues.push(...issues);
}
if (this.label) {
issues.forEach(issue => issue.addLabel(this.label));
}
if (this.assignee) {
if (listFrom && listFrom.type === 'assignee') {
issues.forEach(issue => issue.removeAssignee(listFrom.assignee));
}
issues.forEach(issue => issue.addAssignee(this.assignee));
}
if (IS_EE && this.milestone) {
if (listFrom && listFrom.type === 'milestone') {
issues.forEach(issue => issue.removeMilestone(listFrom.milestone));
}
issues.forEach(issue => issue.addMilestone(this.milestone));
}
if (listFrom) {
this.issuesSize += issues.length;
this.updateMultipleIssues(issues, listFrom, moveBeforeId, moveAfterId);
}
}
boardsStore.addMultipleListIssues(this, issues, listFrom, newIndex);
}
 
addIssue(issue, listFrom, newIndex) {
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