Merge branch 'master' into 'template-improvements-for-documentation'
# Conflicts: # .gitlab/merge_request_templates/Documentation.md
This commit is contained in:
commit
07c32a0df5
568 changed files with 8302 additions and 2273 deletions
|
@ -509,6 +509,7 @@ rspec-mysql:
|
|||
parallel: 50
|
||||
|
||||
.rspec-quarantine: &rspec-quarantine
|
||||
retry: 0
|
||||
script:
|
||||
- export CACHE_CLASSES=true
|
||||
- scripts/gitaly-test-spawn
|
||||
|
|
|
@ -26,7 +26,7 @@ https://docs.gitlab.com/ce/development/documentation/index.html#changing-documen
|
|||
to the new document if there are any Disqus comments on the old document thread.
|
||||
- [ ] Update the link in `features.yml` (if applicable)
|
||||
- [ ] If working on CE and the `ee-compat-check` jobs fails, submit an MR to EE
|
||||
with the changes as well (https://docs.gitlab.com/ce/development/writing_documentation.html#cherry-picking-from-ce-to-ee).
|
||||
with the changes as well (https://docs.gitlab.com/ce/development/documentation/index.html#cherry-picking-from-ce-to-ee).
|
||||
- [ ] Ping one of the technical writers for review.
|
||||
|
||||
/label ~Documentation
|
||||
|
|
|
@ -16,7 +16,7 @@ Add a description of your merge request here.
|
|||
|
||||
## Database checklist
|
||||
|
||||
- [ ] Conforms to the [database guides](https://docs.gitlab.com/ee/development/README.html#databases-guides)
|
||||
- [ ] Conforms to the [database guides](https://docs.gitlab.com/ee/development/README.html#database-guides)
|
||||
|
||||
When adding migrations:
|
||||
|
||||
|
@ -49,10 +49,10 @@ When removing columns, tables, indexes or other structures:
|
|||
## General checklist
|
||||
|
||||
- [ ] [Changelog entry](https://docs.gitlab.com/ee/development/changelog.html) added, if necessary
|
||||
- [ ] [Documentation created/updated](https://docs.gitlab.com/ee/development/documentation/index.html#contributing-to-docs)
|
||||
- [ ] [Documentation created/updated](https://docs.gitlab.com/ee/development/documentation/)
|
||||
- [ ] [Tests added for this feature/bug](https://docs.gitlab.com/ee/development/testing_guide/index.html)
|
||||
- [ ] Conforms to the [code review guidelines](https://docs.gitlab.com/ee/development/code_review.html)
|
||||
- [ ] Conforms to the [merge request performance guidelines](https://docs.gitlab.com/ee/development/merge_request_performance_guidelines.html)
|
||||
- [ ] Conforms to the [style guides](https://gitlab.com/gitlab-org/gitlab-ee/blob/master/CONTRIBUTING.md#style-guides)
|
||||
- [ ] Conforms to the [style guides](https://docs.gitlab.com/ee/development/contributing/style_guides.html)
|
||||
|
||||
/label ~database
|
||||
|
|
30
.stylelintrc
Normal file
30
.stylelintrc
Normal file
|
@ -0,0 +1,30 @@
|
|||
{
|
||||
"extends": "stylelint-config-recommended",
|
||||
"plugins": [
|
||||
"stylelint-scss"
|
||||
],
|
||||
"rules": {
|
||||
"no-descending-specificity": null,
|
||||
"font-family-no-missing-generic-family-keyword": null,
|
||||
"at-rule-no-unknown": [ true, {
|
||||
ignoreAtRules: ["include", "each", "mixin", "extend", "if", "function", "for", "else", "return"]
|
||||
}],
|
||||
"selector-type-no-unknown": [true, {
|
||||
"ignoreTypes": ["gl-emoji"]
|
||||
}],
|
||||
"unit-no-unknown" : [true, {
|
||||
"ignoreFunctions": ["-webkit-image-set"]
|
||||
}],
|
||||
"scss/at-extend-no-missing-placeholder": null,
|
||||
"scss/at-function-pattern": "^[a-z]+([a-z0-9-]+[a-z0-9]+)?$",
|
||||
"scss/at-import-no-partial-leading-underscore": true,
|
||||
"scss/at-import-partial-extension-blacklist": ["scss"],
|
||||
"scss/at-mixin-pattern": "^[a-z]+([a-z0-9-]+[a-z0-9]+)?$",
|
||||
"scss/at-rule-no-unknown": true,
|
||||
"scss/dollar-variable-colon-space-after": "always",
|
||||
"scss/dollar-variable-colon-space-before": "never",
|
||||
"scss/dollar-variable-pattern": "^[_]?[a-z]+([a-z0-9-]+[a-z0-9]+)?$",
|
||||
"scss/percent-placeholder-pattern": "^[a-z]+([a-z0-9-]+[a-z0-9]+)?$",
|
||||
"scss/selector-no-redundant-nesting-selector": true,
|
||||
}
|
||||
}
|
|
@ -11,3 +11,4 @@ danger.import_dangerfile(path: 'danger/commit_messages')
|
|||
danger.import_dangerfile(path: 'danger/duplicate_yarn_dependencies')
|
||||
danger.import_dangerfile(path: 'danger/prettier')
|
||||
danger.import_dangerfile(path: 'danger/eslint')
|
||||
danger.import_dangerfile(path: 'danger/roulette')
|
||||
|
|
|
@ -1 +1 @@
|
|||
1.19.0
|
||||
1.20.0
|
||||
|
|
|
@ -1 +1 @@
|
|||
8.3.0
|
||||
8.3.1
|
||||
|
|
4
Gemfile
4
Gemfile
|
@ -143,7 +143,7 @@ gem 'diffy', '~> 3.1.0'
|
|||
gem 'rack', '2.0.6'
|
||||
|
||||
group :unicorn do
|
||||
gem 'unicorn', '~> 5.1.0'
|
||||
gem 'unicorn', '~> 5.4.1'
|
||||
gem 'unicorn-worker-killer', '~> 0.4.4'
|
||||
end
|
||||
|
||||
|
@ -410,7 +410,7 @@ gem 'sys-filesystem', '~> 1.1.6'
|
|||
|
||||
# SSH host key support
|
||||
gem 'net-ssh', '~> 5.0'
|
||||
gem 'sshkey', '~> 1.9.0'
|
||||
gem 'sshkey', '~> 2.0'
|
||||
|
||||
# Required for ED25519 SSH host key support
|
||||
group :ed25519 do
|
||||
|
|
12
Gemfile.lock
12
Gemfile.lock
|
@ -422,7 +422,7 @@ GEM
|
|||
activerecord
|
||||
kaminari-core (= 1.0.1)
|
||||
kaminari-core (1.0.1)
|
||||
kgio (2.10.0)
|
||||
kgio (2.11.2)
|
||||
knapsack (1.17.0)
|
||||
rake
|
||||
kubeclient (4.2.2)
|
||||
|
@ -666,7 +666,7 @@ GEM
|
|||
rake (>= 0.8.7)
|
||||
thor (>= 0.18.1, < 2.0)
|
||||
rainbow (3.0.0)
|
||||
raindrops (0.18.0)
|
||||
raindrops (0.19.0)
|
||||
rake (12.3.2)
|
||||
rb-fsevent (0.10.2)
|
||||
rb-inotify (0.9.10)
|
||||
|
@ -855,7 +855,7 @@ GEM
|
|||
activesupport (>= 4.0)
|
||||
sprockets (>= 3.0.0)
|
||||
sqlite3 (1.3.13)
|
||||
sshkey (1.9.0)
|
||||
sshkey (2.0.0)
|
||||
stackprof (0.2.10)
|
||||
state_machines (0.5.0)
|
||||
state_machines-activemodel (0.5.1)
|
||||
|
@ -898,7 +898,7 @@ GEM
|
|||
unf_ext
|
||||
unf_ext (0.0.7.5)
|
||||
unicode-display_width (1.3.2)
|
||||
unicorn (5.1.0)
|
||||
unicorn (5.4.1)
|
||||
kgio (~> 2.6)
|
||||
raindrops (~> 0.7)
|
||||
unicorn-worker-killer (0.4.4)
|
||||
|
@ -1157,7 +1157,7 @@ DEPENDENCIES
|
|||
spring (~> 2.0.0)
|
||||
spring-commands-rspec (~> 1.0.4)
|
||||
sprockets (~> 3.7.0)
|
||||
sshkey (~> 1.9.0)
|
||||
sshkey (~> 2.0)
|
||||
stackprof (~> 0.2.10)
|
||||
state_machines-activerecord (~> 0.5.1)
|
||||
sys-filesystem (~> 1.1.6)
|
||||
|
@ -1169,7 +1169,7 @@ DEPENDENCIES
|
|||
u2f (~> 0.2.1)
|
||||
uglifier (~> 2.7.2)
|
||||
unf (~> 0.1.4)
|
||||
unicorn (~> 5.1.0)
|
||||
unicorn (~> 5.4.1)
|
||||
unicorn-worker-killer (~> 0.4.4)
|
||||
validates_hostname (~> 1.0.6)
|
||||
version_sorter (~> 2.1.0)
|
||||
|
|
14
PROCESS.md
14
PROCESS.md
|
@ -108,7 +108,19 @@ Merge requests that make changes hidden behind a feature flag, or remove an
|
|||
existing feature flag because a feature is deemed stable, may be merged (and
|
||||
picked into the stable branches) up to the 19th of the month. Such merge
|
||||
requests should have the ~"feature flag" label assigned, and don't require a
|
||||
corresponding exception request to be created.
|
||||
corresponding exception request to be created.
|
||||
|
||||
A level of common sense should be applied when deciding whether to have a feature
|
||||
behind a feature flag off or on by default.
|
||||
|
||||
The following guideliness can be applied to help make this decision:
|
||||
|
||||
* If the feature is not fully ready or functioning, the feature flag should be disabled by default.
|
||||
* If the feature is ready but there are concerns about performance or impact, the feature flag should be enabled by default, but
|
||||
disabled via chatops before deployment on GitLab.com environments. If the performance concern is confirmed, the final release should have the feature flag disabled by default.
|
||||
* In most other cases, the feature flag can be enabled by default.
|
||||
|
||||
For more information on rolling out changes using feature flags, read [through the documentation](https://docs.gitlab.com/ee/development/rolling_out_changes_using_feature_flags.html).
|
||||
|
||||
In order to build the final package and present the feature for self-hosted
|
||||
customers, the feature flag should be removed. This should happen before the
|
||||
|
|
|
@ -36,13 +36,20 @@ export class CopyAsGFM {
|
|||
div.appendChild(el.cloneNode(true));
|
||||
const html = div.innerHTML;
|
||||
|
||||
clipboardData.setData('text/plain', el.textContent);
|
||||
clipboardData.setData('text/html', html);
|
||||
// We are also setting this as fallback to transform the selection to gfm on paste
|
||||
clipboardData.setData('text/x-gfm-html', html);
|
||||
|
||||
CopyAsGFM.nodeToGFM(el)
|
||||
.then(res => {
|
||||
clipboardData.setData('text/plain', el.textContent);
|
||||
clipboardData.setData('text/x-gfm', res);
|
||||
clipboardData.setData('text/html', html);
|
||||
})
|
||||
.catch(() => {});
|
||||
.catch(() => {
|
||||
// Not showing the error as Firefox might doesn't allow
|
||||
// it or other browsers who have a time limit on the execution
|
||||
// of the copy event
|
||||
});
|
||||
}
|
||||
|
||||
static pasteGFM(e) {
|
||||
|
@ -51,11 +58,28 @@ export class CopyAsGFM {
|
|||
|
||||
const text = clipboardData.getData('text/plain');
|
||||
const gfm = clipboardData.getData('text/x-gfm');
|
||||
if (!gfm) return;
|
||||
const gfmHtml = clipboardData.getData('text/x-gfm-html');
|
||||
if (!gfm && !gfmHtml) return;
|
||||
|
||||
e.preventDefault();
|
||||
|
||||
window.gl.utils.insertText(e.target, textBefore => {
|
||||
// We have the original selection already converted to gfm
|
||||
if (gfm) {
|
||||
CopyAsGFM.insertPastedText(e.target, text, gfm);
|
||||
} else {
|
||||
// Due to the async copy call we are not able to produce gfm so we transform the cached HTML
|
||||
const div = document.createElement('div');
|
||||
div.innerHTML = gfmHtml;
|
||||
CopyAsGFM.nodeToGFM(div)
|
||||
.then(transformedGfm => {
|
||||
CopyAsGFM.insertPastedText(e.target, text, transformedGfm);
|
||||
})
|
||||
.catch(() => {});
|
||||
}
|
||||
}
|
||||
|
||||
static insertPastedText(target, text, gfm) {
|
||||
window.gl.utils.insertText(target, textBefore => {
|
||||
// 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.
|
||||
|
|
|
@ -221,7 +221,7 @@ export default {
|
|||
</script>
|
||||
|
||||
<template>
|
||||
<div class="board-list-component d-flex flex-column">
|
||||
<div class="board-list-component">
|
||||
<div v-if="loading" class="board-list-loading text-center" aria-label="Loading issues">
|
||||
<gl-loading-icon />
|
||||
</div>
|
||||
|
|
|
@ -4,6 +4,7 @@ import Icon from '~/vue_shared/components/icon.vue';
|
|||
import { __ } from '~/locale';
|
||||
import createFlash from '~/flash';
|
||||
import { GlLoadingIcon } from '@gitlab/ui';
|
||||
import PanelResizer from '~/vue_shared/components/panel_resizer.vue';
|
||||
import eventHub from '../../notes/event_hub';
|
||||
import CompareVersions from './compare_versions.vue';
|
||||
import DiffFile from './diff_file.vue';
|
||||
|
@ -11,6 +12,13 @@ import NoChanges from './no_changes.vue';
|
|||
import HiddenFilesWarning from './hidden_files_warning.vue';
|
||||
import CommitWidget from './commit_widget.vue';
|
||||
import TreeList from './tree_list.vue';
|
||||
import {
|
||||
TREE_LIST_WIDTH_STORAGE_KEY,
|
||||
INITIAL_TREE_WIDTH,
|
||||
MIN_TREE_WIDTH,
|
||||
MAX_TREE_WIDTH,
|
||||
TREE_HIDE_STATS_WIDTH,
|
||||
} from '../constants';
|
||||
|
||||
export default {
|
||||
name: 'DiffsApp',
|
||||
|
@ -23,6 +31,7 @@ export default {
|
|||
CommitWidget,
|
||||
TreeList,
|
||||
GlLoadingIcon,
|
||||
PanelResizer,
|
||||
},
|
||||
props: {
|
||||
endpoint: {
|
||||
|
@ -54,8 +63,12 @@ export default {
|
|||
},
|
||||
},
|
||||
data() {
|
||||
const treeWidth =
|
||||
parseInt(localStorage.getItem(TREE_LIST_WIDTH_STORAGE_KEY), 10) || INITIAL_TREE_WIDTH;
|
||||
|
||||
return {
|
||||
assignedDiscussions: false,
|
||||
treeWidth,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
|
@ -96,6 +109,9 @@ export default {
|
|||
this.startVersion.version_index === this.mergeRequestDiff.version_index)
|
||||
);
|
||||
},
|
||||
hideFileStats() {
|
||||
return this.treeWidth <= TREE_HIDE_STATS_WIDTH;
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
diffViewType() {
|
||||
|
@ -142,6 +158,7 @@ export default {
|
|||
'startRenderDiffsQueue',
|
||||
'assignDiscussionsToDiff',
|
||||
'setHighlightedRow',
|
||||
'cacheTreeListWidth',
|
||||
]),
|
||||
fetchData() {
|
||||
this.fetchDiffFiles()
|
||||
|
@ -184,6 +201,8 @@ export default {
|
|||
}
|
||||
},
|
||||
},
|
||||
minTreeWidth: MIN_TREE_WIDTH,
|
||||
maxTreeWidth: MAX_TREE_WIDTH,
|
||||
};
|
||||
</script>
|
||||
|
||||
|
@ -209,7 +228,21 @@ export default {
|
|||
:data-can-create-note="getNoteableData.current_user.can_create_note"
|
||||
class="files d-flex prepend-top-default"
|
||||
>
|
||||
<div v-show="showTreeList" class="diff-tree-list"><tree-list /></div>
|
||||
<div
|
||||
v-show="showTreeList"
|
||||
:style="{ width: `${treeWidth}px` }"
|
||||
class="diff-tree-list js-diff-tree-list"
|
||||
>
|
||||
<panel-resizer
|
||||
:size.sync="treeWidth"
|
||||
:start-size="treeWidth"
|
||||
:min-size="$options.minTreeWidth"
|
||||
:max-size="$options.maxTreeWidth"
|
||||
side="right"
|
||||
@resize-end="cacheTreeListWidth"
|
||||
/>
|
||||
<tree-list :hide-file-stats="hideFileStats" />
|
||||
</div>
|
||||
<div class="diff-files-holder">
|
||||
<commit-widget v-if="commit" :commit="commit" />
|
||||
<template v-if="renderDiffFiles">
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
<script>
|
||||
import { mapActions, mapGetters, mapState } from 'vuex';
|
||||
import DiffViewer from '~/vue_shared/components/diff_viewer/diff_viewer.vue';
|
||||
import EmptyFileViewer from '~/vue_shared/components/diff_viewer/viewers/empty_file.vue';
|
||||
import NotDiffableViewer from '~/vue_shared/components/diff_viewer/viewers/not_diffable.vue';
|
||||
import NoPreviewViewer from '~/vue_shared/components/diff_viewer/viewers/no_preview.vue';
|
||||
import InlineDiffView from './inline_diff_view.vue';
|
||||
import ParallelDiffView from './parallel_diff_view.vue';
|
||||
import NoteForm from '../../notes/components/note_form.vue';
|
||||
|
@ -9,6 +10,7 @@ import ImageDiffOverlay from './image_diff_overlay.vue';
|
|||
import DiffDiscussions from './diff_discussions.vue';
|
||||
import { IMAGE_DIFF_POSITION_TYPE } from '../constants';
|
||||
import { getDiffMode } from '../store/utils';
|
||||
import { diffViewerModes } from '~/ide/constants';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
|
@ -18,7 +20,8 @@ export default {
|
|||
NoteForm,
|
||||
DiffDiscussions,
|
||||
ImageDiffOverlay,
|
||||
EmptyFileViewer,
|
||||
NotDiffableViewer,
|
||||
NoPreviewViewer,
|
||||
},
|
||||
props: {
|
||||
diffFile: {
|
||||
|
@ -42,11 +45,17 @@ export default {
|
|||
diffMode() {
|
||||
return getDiffMode(this.diffFile);
|
||||
},
|
||||
isTextFile() {
|
||||
return this.diffFile.viewer.name === 'text';
|
||||
diffViewerMode() {
|
||||
return this.diffFile.viewer.name;
|
||||
},
|
||||
errorMessage() {
|
||||
return this.diffFile.viewer.error;
|
||||
isTextFile() {
|
||||
return this.diffViewerMode === diffViewerModes.text;
|
||||
},
|
||||
noPreview() {
|
||||
return this.diffViewerMode === diffViewerModes.no_preview;
|
||||
},
|
||||
notDiffable() {
|
||||
return this.diffViewerMode === diffViewerModes.not_diffable;
|
||||
},
|
||||
diffFileCommentForm() {
|
||||
return this.getCommentFormForDiffFile(this.diffFile.file_hash);
|
||||
|
@ -78,11 +87,10 @@ export default {
|
|||
|
||||
<template>
|
||||
<div class="diff-content">
|
||||
<div v-if="!errorMessage" class="diff-viewer">
|
||||
<div class="diff-viewer">
|
||||
<template v-if="isTextFile">
|
||||
<empty-file-viewer v-if="diffFile.empty" />
|
||||
<inline-diff-view
|
||||
v-else-if="isInlineView"
|
||||
v-if="isInlineView"
|
||||
:diff-file="diffFile"
|
||||
:diff-lines="diffFile.highlighted_diff_lines || []"
|
||||
:help-page-path="helpPagePath"
|
||||
|
@ -94,9 +102,12 @@ export default {
|
|||
:help-page-path="helpPagePath"
|
||||
/>
|
||||
</template>
|
||||
<not-diffable-viewer v-else-if="notDiffable" />
|
||||
<no-preview-viewer v-else-if="noPreview" />
|
||||
<diff-viewer
|
||||
v-else
|
||||
:diff-mode="diffMode"
|
||||
:diff-viewer-mode="diffViewerMode"
|
||||
:new-path="diffFile.new_path"
|
||||
:new-sha="diffFile.diff_refs.head_sha"
|
||||
:old-path="diffFile.old_path"
|
||||
|
@ -132,8 +143,5 @@ export default {
|
|||
</div>
|
||||
</diff-viewer>
|
||||
</div>
|
||||
<div v-else class="diff-viewer">
|
||||
<div class="nothing-here-block" v-html="errorMessage"></div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
|
|
@ -7,6 +7,7 @@ import { GlLoadingIcon } from '@gitlab/ui';
|
|||
import eventHub from '../../notes/event_hub';
|
||||
import DiffFileHeader from './diff_file_header.vue';
|
||||
import DiffContent from './diff_content.vue';
|
||||
import { diffViewerErrors } from '~/ide/constants';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
|
@ -33,15 +34,13 @@ export default {
|
|||
return {
|
||||
isLoadingCollapsedDiff: false,
|
||||
forkMessageVisible: false,
|
||||
isCollapsed: this.file.viewer.collapsed || false,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
...mapState('diffs', ['currentDiffFileId']),
|
||||
...mapGetters(['isNotesFetched']),
|
||||
...mapGetters('diffs', ['getDiffFileDiscussions']),
|
||||
isCollapsed() {
|
||||
return this.file.collapsed || false;
|
||||
},
|
||||
viewBlobLink() {
|
||||
return sprintf(
|
||||
__('You can %{linkStart}view the blob%{linkEnd} instead.'),
|
||||
|
@ -52,17 +51,6 @@ export default {
|
|||
false,
|
||||
);
|
||||
},
|
||||
showExpandMessage() {
|
||||
return (
|
||||
this.isCollapsed ||
|
||||
(!this.file.highlighted_diff_lines &&
|
||||
!this.isLoadingCollapsedDiff &&
|
||||
!this.file.too_large &&
|
||||
this.file.text &&
|
||||
!this.file.renamed_file &&
|
||||
!this.file.mode_changed)
|
||||
);
|
||||
},
|
||||
showLoadingIcon() {
|
||||
return this.isLoadingCollapsedDiff || (!this.file.renderIt && !this.isCollapsed);
|
||||
},
|
||||
|
@ -73,9 +61,15 @@ export default {
|
|||
this.file.parallel_diff_lines.length > 0
|
||||
);
|
||||
},
|
||||
isFileTooLarge() {
|
||||
return this.file.viewer.error === diffViewerErrors.too_large;
|
||||
},
|
||||
errorMessage() {
|
||||
return this.file.viewer.error_message;
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
'file.collapsed': function fileCollapsedWatch(newVal, oldVal) {
|
||||
isCollapsed: function fileCollapsedWatch(newVal, oldVal) {
|
||||
if (!newVal && oldVal && !this.hasDiffLines) {
|
||||
this.handleLoadCollapsedDiff();
|
||||
}
|
||||
|
@ -85,13 +79,13 @@ export default {
|
|||
eventHub.$on(`loadCollapsedDiff/${this.file.file_hash}`, this.handleLoadCollapsedDiff);
|
||||
},
|
||||
methods: {
|
||||
...mapActions('diffs', ['loadCollapsedDiff', 'assignDiscussionsToDiff']),
|
||||
...mapActions('diffs', ['loadCollapsedDiff', 'assignDiscussionsToDiff', 'setRenderIt']),
|
||||
handleToggle() {
|
||||
if (!this.hasDiffLines) {
|
||||
this.handleLoadCollapsedDiff();
|
||||
} else {
|
||||
this.file.collapsed = !this.file.collapsed;
|
||||
this.file.renderIt = true;
|
||||
this.isCollapsed = !this.isCollapsed;
|
||||
this.setRenderIt(this.file);
|
||||
}
|
||||
},
|
||||
handleLoadCollapsedDiff() {
|
||||
|
@ -100,8 +94,8 @@ export default {
|
|||
this.loadCollapsedDiff(this.file)
|
||||
.then(() => {
|
||||
this.isLoadingCollapsedDiff = false;
|
||||
this.file.collapsed = false;
|
||||
this.file.renderIt = true;
|
||||
this.isCollapsed = false;
|
||||
this.setRenderIt(this.file);
|
||||
})
|
||||
.then(() => {
|
||||
requestIdleCallback(
|
||||
|
@ -164,21 +158,25 @@ export default {
|
|||
Cancel
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<diff-content
|
||||
v-if="!isCollapsed && file.renderIt"
|
||||
:class="{ hidden: isCollapsed || file.too_large }"
|
||||
:diff-file="file"
|
||||
:help-page-path="helpPagePath"
|
||||
/>
|
||||
<gl-loading-icon v-if="showLoadingIcon" class="diff-content loading" />
|
||||
<div v-else-if="showExpandMessage" class="nothing-here-block diff-collapsed">
|
||||
{{ __('This diff is collapsed.') }}
|
||||
<a class="click-to-expand js-click-to-expand" href="#" @click.prevent="handleToggle">{{
|
||||
__('Click to expand it.')
|
||||
}}</a>
|
||||
</div>
|
||||
<div v-if="file.too_large" class="nothing-here-block diff-collapsed js-too-large-diff">
|
||||
<template v-else>
|
||||
<div v-if="errorMessage" class="diff-viewer">
|
||||
<div class="nothing-here-block" v-html="errorMessage"></div>
|
||||
</div>
|
||||
<div v-else-if="isCollapsed" class="nothing-here-block diff-collapsed">
|
||||
{{ __('This diff is collapsed.') }}
|
||||
<a class="click-to-expand js-click-to-expand" href="#" @click.prevent="handleToggle">{{
|
||||
__('Click to expand it.')
|
||||
}}</a>
|
||||
</div>
|
||||
<diff-content
|
||||
v-else
|
||||
:class="{ hidden: isCollapsed || isFileTooLarge }"
|
||||
:diff-file="file"
|
||||
:help-page-path="helpPagePath"
|
||||
/>
|
||||
</template>
|
||||
<div v-if="isFileTooLarge" class="nothing-here-block diff-collapsed js-too-large-diff">
|
||||
{{ __('This source diff could not be displayed because it is too large.') }}
|
||||
<span v-html="viewBlobLink"></span>
|
||||
</div>
|
||||
|
|
|
@ -8,6 +8,7 @@ import FileIcon from '~/vue_shared/components/file_icon.vue';
|
|||
import { GlTooltipDirective } from '@gitlab/ui';
|
||||
import { truncateSha } from '~/lib/utils/text_utility';
|
||||
import { __, s__, sprintf } from '~/locale';
|
||||
import { diffViewerModes } from '~/ide/constants';
|
||||
import EditButton from './edit_button.vue';
|
||||
import DiffStats from './diff_stats.vue';
|
||||
|
||||
|
@ -118,6 +119,12 @@ export default {
|
|||
gfmCopyText() {
|
||||
return `\`${this.diffFile.file_path}\``;
|
||||
},
|
||||
isFileRenamed() {
|
||||
return this.diffFile.viewer.name === diffViewerModes.renamed;
|
||||
},
|
||||
isModeChanged() {
|
||||
return this.diffFile.viewer.name === diffViewerModes.mode_changed;
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
polyfillSticky(this.$refs.header);
|
||||
|
@ -165,7 +172,7 @@ export default {
|
|||
aria-hidden="true"
|
||||
css-classes="js-file-icon append-right-5"
|
||||
/>
|
||||
<span v-if="diffFile.renamed_file">
|
||||
<span v-if="isFileRenamed">
|
||||
<strong
|
||||
v-gl-tooltip
|
||||
:title="diffFile.old_path"
|
||||
|
@ -193,7 +200,7 @@ export default {
|
|||
css-class="btn-default btn-transparent btn-clipboard"
|
||||
/>
|
||||
|
||||
<small v-if="diffFile.mode_changed" ref="fileMode">
|
||||
<small v-if="isModeChanged" ref="fileMode">
|
||||
{{ diffFile.a_mode }} → {{ diffFile.b_mode }}
|
||||
</small>
|
||||
|
||||
|
|
|
@ -13,6 +13,12 @@ export default {
|
|||
Icon,
|
||||
FileRow,
|
||||
},
|
||||
props: {
|
||||
hideFileStats: {
|
||||
type: Boolean,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
search: '',
|
||||
|
@ -40,6 +46,9 @@ export default {
|
|||
return acc;
|
||||
}, []);
|
||||
},
|
||||
fileRowExtraComponent() {
|
||||
return this.hideFileStats ? null : FileRowStats;
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
...mapActions('diffs', ['toggleTreeOpen', 'scrollToFile', 'toggleFileFinder']),
|
||||
|
@ -48,7 +57,6 @@ export default {
|
|||
},
|
||||
},
|
||||
shortcutKeyCharacter: `${/Mac/i.test(navigator.userAgent) ? '⌘' : 'Ctrl'}+P`,
|
||||
FileRowStats,
|
||||
diffTreeFiltering: gon.features && gon.features.diffTreeFiltering,
|
||||
};
|
||||
</script>
|
||||
|
@ -98,7 +106,7 @@ export default {
|
|||
:file="file"
|
||||
:level="0"
|
||||
:hide-extra-on-tree="true"
|
||||
:extra-component="$options.FileRowStats"
|
||||
:extra-component="fileRowExtraComponent"
|
||||
:show-changed-icon="true"
|
||||
@toggleTreeOpen="toggleTreeOpen"
|
||||
@clickFile="scrollToFile"
|
||||
|
|
|
@ -36,3 +36,9 @@ export const MR_TREE_SHOW_KEY = 'mr_tree_show';
|
|||
export const TREE_TYPE = 'tree';
|
||||
export const TREE_LIST_STORAGE_KEY = 'mr_diff_tree_list';
|
||||
export const WHITESPACE_STORAGE_KEY = 'mr_show_whitespace';
|
||||
export const TREE_LIST_WIDTH_STORAGE_KEY = 'mr_tree_list_width';
|
||||
|
||||
export const INITIAL_TREE_WIDTH = 320;
|
||||
export const MIN_TREE_WIDTH = 240;
|
||||
export const MAX_TREE_WIDTH = 400;
|
||||
export const TREE_HIDE_STATS_WIDTH = 260;
|
||||
|
|
|
@ -16,7 +16,9 @@ import {
|
|||
MR_TREE_SHOW_KEY,
|
||||
TREE_LIST_STORAGE_KEY,
|
||||
WHITESPACE_STORAGE_KEY,
|
||||
TREE_LIST_WIDTH_STORAGE_KEY,
|
||||
} from '../constants';
|
||||
import { diffViewerModes } from '~/ide/constants';
|
||||
|
||||
export const setBaseConfig = ({ commit }, options) => {
|
||||
const { endpoint, projectPath } = options;
|
||||
|
@ -91,7 +93,7 @@ export const renderFileForDiscussionId = ({ commit, rootState, state }, discussi
|
|||
commit(types.RENDER_FILE, file);
|
||||
}
|
||||
|
||||
if (file.collapsed) {
|
||||
if (file.viewer.collapsed) {
|
||||
eventHub.$emit(`loadCollapsedDiff/${file.file_hash}`);
|
||||
scrollToElement(document.getElementById(file.file_hash));
|
||||
} else {
|
||||
|
@ -105,7 +107,8 @@ export const startRenderDiffsQueue = ({ state, commit }) => {
|
|||
const checkItem = () =>
|
||||
new Promise(resolve => {
|
||||
const nextFile = state.diffFiles.find(
|
||||
file => !file.renderIt && (!file.collapsed || !file.text),
|
||||
file =>
|
||||
!file.renderIt && (!file.viewer.collapsed || !file.viewer.name === diffViewerModes.text),
|
||||
);
|
||||
|
||||
if (nextFile) {
|
||||
|
@ -128,6 +131,8 @@ export const startRenderDiffsQueue = ({ state, commit }) => {
|
|||
return checkItem();
|
||||
};
|
||||
|
||||
export const setRenderIt = ({ commit }, file) => commit(types.RENDER_FILE, file);
|
||||
|
||||
export const setInlineDiffViewType = ({ commit }) => {
|
||||
commit(types.SET_DIFF_VIEW_TYPE, INLINE_DIFF_VIEW_TYPE);
|
||||
|
||||
|
@ -300,5 +305,9 @@ export const toggleFileFinder = ({ commit }, visible) => {
|
|||
commit(types.TOGGLE_FILE_FINDER_VISIBLE, visible);
|
||||
};
|
||||
|
||||
export const cacheTreeListWidth = (_, size) => {
|
||||
localStorage.setItem(TREE_LIST_WIDTH_STORAGE_KEY, size);
|
||||
};
|
||||
|
||||
// prevent babel-plugin-rewire from generating an invalid default during karma tests
|
||||
export default () => {};
|
||||
|
|
|
@ -4,7 +4,8 @@ export const isParallelView = state => state.diffViewType === PARALLEL_DIFF_VIEW
|
|||
|
||||
export const isInlineView = state => state.diffViewType === INLINE_DIFF_VIEW_TYPE;
|
||||
|
||||
export const hasCollapsedFile = state => state.diffFiles.some(file => file.collapsed);
|
||||
export const hasCollapsedFile = state =>
|
||||
state.diffFiles.some(file => file.viewer && file.viewer.collapsed);
|
||||
|
||||
export const commitId = state => (state.commit && state.commit.id ? state.commit.id : null);
|
||||
|
||||
|
|
|
@ -144,6 +144,7 @@ export default {
|
|||
|
||||
if (left || right) {
|
||||
return {
|
||||
...line,
|
||||
left: line.left ? mapDiscussions(line.left) : null,
|
||||
right: line.right ? mapDiscussions(line.right, () => !left) : null,
|
||||
};
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import _ from 'underscore';
|
||||
import { diffModes } from '~/ide/constants';
|
||||
import { truncatePathMiddleToLength } from '~/lib/utils/text_utility';
|
||||
import { diffModes, diffViewerModes } from '~/ide/constants';
|
||||
import {
|
||||
LINE_POSITION_LEFT,
|
||||
LINE_POSITION_RIGHT,
|
||||
|
@ -161,6 +161,7 @@ export function addContextLines(options) {
|
|||
const normalizedParallelLines = contextLines.map(line => ({
|
||||
left: line,
|
||||
right: line,
|
||||
line_code: line.line_code,
|
||||
}));
|
||||
|
||||
if (options.bottom) {
|
||||
|
@ -247,7 +248,8 @@ export function prepareDiffData(diffData) {
|
|||
|
||||
Object.assign(file, {
|
||||
renderIt: showingLines < LINES_TO_BE_RENDERED_DIRECTLY,
|
||||
collapsed: file.text && showingLines > MAX_LINES_TO_BE_RENDERED,
|
||||
collapsed:
|
||||
file.viewer.name === diffViewerModes.text && showingLines > MAX_LINES_TO_BE_RENDERED,
|
||||
discussions: [],
|
||||
});
|
||||
}
|
||||
|
@ -403,7 +405,9 @@ export const getDiffMode = diffFile => {
|
|||
const diffModeKey = Object.keys(diffModes).find(key => diffFile[`${key}_file`]);
|
||||
return (
|
||||
diffModes[diffModeKey] ||
|
||||
(diffFile.mode_changed && diffModes.mode_changed) ||
|
||||
(diffFile.viewer &&
|
||||
diffFile.viewer.name === diffViewerModes.mode_changed &&
|
||||
diffViewerModes.mode_changed) ||
|
||||
diffModes.replaced
|
||||
);
|
||||
};
|
||||
|
|
63
app/assets/javascripts/emoji/no_emoji_validator.js
Normal file
63
app/assets/javascripts/emoji/no_emoji_validator.js
Normal file
|
@ -0,0 +1,63 @@
|
|||
import { __ } from '~/locale';
|
||||
import emojiRegex from 'emoji-regex';
|
||||
|
||||
const invalidInputClass = 'gl-field-error-outline';
|
||||
|
||||
export default class NoEmojiValidator {
|
||||
constructor(opts = {}) {
|
||||
const container = opts.container || '';
|
||||
this.noEmojiEmelents = document.querySelectorAll(`${container} .js-block-emoji`);
|
||||
|
||||
this.noEmojiEmelents.forEach(element =>
|
||||
element.addEventListener('input', this.eventHandler.bind(this)),
|
||||
);
|
||||
}
|
||||
|
||||
eventHandler(event) {
|
||||
this.inputDomElement = event.target;
|
||||
this.inputErrorMessage = this.inputDomElement.nextSibling;
|
||||
|
||||
const { value } = this.inputDomElement;
|
||||
|
||||
this.validatePattern(value);
|
||||
this.setValidationStateAndMessage();
|
||||
}
|
||||
|
||||
validatePattern(value) {
|
||||
const pattern = emojiRegex();
|
||||
this.hasEmojis = new RegExp(pattern).test(value);
|
||||
|
||||
if (this.hasEmojis) {
|
||||
this.inputDomElement.setCustomValidity(__('Invalid input, please avoid emojis'));
|
||||
} else {
|
||||
this.inputDomElement.setCustomValidity('');
|
||||
}
|
||||
}
|
||||
|
||||
setValidationStateAndMessage() {
|
||||
if (!this.inputDomElement.checkValidity()) {
|
||||
this.setInvalidState();
|
||||
} else {
|
||||
this.clearFieldValidationState();
|
||||
}
|
||||
}
|
||||
|
||||
clearFieldValidationState() {
|
||||
this.inputDomElement.classList.remove(invalidInputClass);
|
||||
this.inputErrorMessage.classList.add('hide');
|
||||
}
|
||||
|
||||
setInvalidState() {
|
||||
this.inputDomElement.classList.add(invalidInputClass);
|
||||
this.setErrorMessage();
|
||||
}
|
||||
|
||||
setErrorMessage() {
|
||||
if (this.hasEmojis) {
|
||||
this.inputErrorMessage.innerHTML = this.inputDomElement.validationMessage;
|
||||
} else {
|
||||
this.inputErrorMessage.innerHTML = this.inputDomElement.title;
|
||||
}
|
||||
this.inputErrorMessage.classList.remove('hide');
|
||||
}
|
||||
}
|
|
@ -163,7 +163,7 @@ export default class FilteredSearchVisualTokens {
|
|||
const tokenValueElement = tokenValueContainer.querySelector('.value');
|
||||
tokenValueElement.innerText = tokenValue;
|
||||
|
||||
if (tokenValue === 'none' || tokenValue === 'any') {
|
||||
if (['none', 'any'].includes(tokenValue.toLowerCase())) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
|
@ -44,7 +44,7 @@ export default {
|
|||
<div class="d-flex ide-commit-editor-header align-items-center">
|
||||
<file-icon :file-name="activeFile.name" :size="16" class="mr-2" />
|
||||
<strong class="mr-2"> {{ activeFile.path }} </strong>
|
||||
<changed-file-icon :file="activeFile" class="ml-0" />
|
||||
<changed-file-icon :file="activeFile" :is-centered="false" />
|
||||
<div class="ml-auto">
|
||||
<button
|
||||
v-if="!isStaged"
|
||||
|
|
|
@ -51,8 +51,11 @@ export default {
|
|||
|
||||
return __('Create file');
|
||||
},
|
||||
isCreatingNew() {
|
||||
return this.entryModal.type !== modalTypes.rename;
|
||||
isCreatingNewFile() {
|
||||
return this.entryModal.type === 'blob';
|
||||
},
|
||||
placeholder() {
|
||||
return this.isCreatingNewFile ? 'dir/file_name' : 'dir/';
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
|
@ -107,9 +110,12 @@ export default {
|
|||
v-model="entryName"
|
||||
type="text"
|
||||
class="form-control qa-full-file-path"
|
||||
placeholder="/dir/file_name"
|
||||
:placeholder="placeholder"
|
||||
/>
|
||||
<ul v-if="isCreatingNew" class="prepend-top-default list-inline qa-template-list">
|
||||
<ul
|
||||
v-if="isCreatingNewFile"
|
||||
class="file-templates prepend-top-default list-inline qa-template-list"
|
||||
>
|
||||
<li v-for="(template, index) in templateTypes" :key="index" class="list-inline-item">
|
||||
<button
|
||||
type="button"
|
||||
|
|
|
@ -24,6 +24,22 @@ export const diffModes = {
|
|||
mode_changed: 'mode_changed',
|
||||
};
|
||||
|
||||
export const diffViewerModes = Object.freeze({
|
||||
not_diffable: 'not_diffable',
|
||||
no_preview: 'no_preview',
|
||||
added: 'added',
|
||||
deleted: 'deleted',
|
||||
renamed: 'renamed',
|
||||
mode_changed: 'mode_changed',
|
||||
text: 'text',
|
||||
image: 'image',
|
||||
});
|
||||
|
||||
export const diffViewerErrors = Object.freeze({
|
||||
too_large: 'too_large',
|
||||
stored_externally: 'server_side_but_stored_externally',
|
||||
});
|
||||
|
||||
export const rightSidebarViews = {
|
||||
pipelines: { name: 'pipelines-list', keepAlive: true },
|
||||
jobsDetail: { name: 'jobs-detail', keepAlive: false },
|
||||
|
|
|
@ -0,0 +1,101 @@
|
|||
<script>
|
||||
import { mapActions, mapState, mapGetters } from 'vuex';
|
||||
import { GlLoadingIcon } from '@gitlab/ui';
|
||||
import LoadingButton from '~/vue_shared/components/loading_button.vue';
|
||||
import { __, sprintf } from '~/locale';
|
||||
import ImportedProjectTableRow from './imported_project_table_row.vue';
|
||||
import ProviderRepoTableRow from './provider_repo_table_row.vue';
|
||||
import eventHub from '../event_hub';
|
||||
|
||||
export default {
|
||||
name: 'ImportProjectsTable',
|
||||
components: {
|
||||
ImportedProjectTableRow,
|
||||
ProviderRepoTableRow,
|
||||
LoadingButton,
|
||||
GlLoadingIcon,
|
||||
},
|
||||
props: {
|
||||
providerTitle: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
|
||||
computed: {
|
||||
...mapState(['importedProjects', 'providerRepos', 'isLoadingRepos']),
|
||||
...mapGetters(['isImportingAnyRepo', 'hasProviderRepos', 'hasImportedProjects']),
|
||||
|
||||
emptyStateText() {
|
||||
return sprintf(__('No %{providerTitle} repositories available to import'), {
|
||||
providerTitle: this.providerTitle,
|
||||
});
|
||||
},
|
||||
|
||||
fromHeaderText() {
|
||||
return sprintf(__('From %{providerTitle}'), { providerTitle: this.providerTitle });
|
||||
},
|
||||
},
|
||||
|
||||
mounted() {
|
||||
return this.fetchRepos();
|
||||
},
|
||||
|
||||
beforeDestroy() {
|
||||
this.stopJobsPolling();
|
||||
this.clearJobsEtagPoll();
|
||||
},
|
||||
|
||||
methods: {
|
||||
...mapActions(['fetchRepos', 'fetchJobs', 'stopJobsPolling', 'clearJobsEtagPoll']),
|
||||
|
||||
importAll() {
|
||||
eventHub.$emit('importAll');
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<div class="d-flex justify-content-between align-items-end flex-wrap mb-3">
|
||||
<p class="light text-nowrap mt-2 my-sm-0">
|
||||
{{ s__('ImportProjects|Select the projects you want to import') }}
|
||||
</p>
|
||||
<loading-button
|
||||
container-class="btn btn-success js-import-all"
|
||||
:loading="isImportingAnyRepo"
|
||||
:label="__('Import all repositories')"
|
||||
:disabled="!hasProviderRepos"
|
||||
type="button"
|
||||
@click="importAll"
|
||||
/>
|
||||
</div>
|
||||
<gl-loading-icon
|
||||
v-if="isLoadingRepos"
|
||||
class="js-loading-button-icon import-projects-loading-icon"
|
||||
:size="4"
|
||||
/>
|
||||
<div v-else-if="hasProviderRepos || hasImportedProjects" class="table-responsive">
|
||||
<table class="table import-table">
|
||||
<thead>
|
||||
<th class="import-jobs-from-col">{{ fromHeaderText }}</th>
|
||||
<th class="import-jobs-to-col">{{ __('To GitLab') }}</th>
|
||||
<th class="import-jobs-status-col">{{ __('Status') }}</th>
|
||||
<th class="import-jobs-cta-col"></th>
|
||||
</thead>
|
||||
<tbody>
|
||||
<imported-project-table-row
|
||||
v-for="project in importedProjects"
|
||||
:key="project.id"
|
||||
:project="project"
|
||||
/>
|
||||
<provider-repo-table-row v-for="repo in providerRepos" :key="repo.id" :repo="repo" />
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div v-else class="text-center">
|
||||
<strong>{{ emptyStateText }}</strong>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
|
@ -0,0 +1,47 @@
|
|||
<script>
|
||||
import { GlLoadingIcon } from '@gitlab/ui';
|
||||
import CiIcon from '~/vue_shared/components/ci_icon.vue';
|
||||
import STATUS_MAP from '../constants';
|
||||
|
||||
export default {
|
||||
name: 'ImportStatus',
|
||||
components: {
|
||||
CiIcon,
|
||||
GlLoadingIcon,
|
||||
},
|
||||
props: {
|
||||
status: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
|
||||
computed: {
|
||||
mappedStatus() {
|
||||
return STATUS_MAP[this.status];
|
||||
},
|
||||
|
||||
ciIconStatus() {
|
||||
const { icon } = this.mappedStatus;
|
||||
|
||||
return {
|
||||
icon: `status_${icon}`,
|
||||
group: icon,
|
||||
};
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<gl-loading-icon
|
||||
v-if="mappedStatus.loadingIcon"
|
||||
:inline="true"
|
||||
:class="mappedStatus.textClass"
|
||||
class="align-middle mr-2"
|
||||
/>
|
||||
<ci-icon v-else css-classes="align-middle mr-2" :status="ciIconStatus" />
|
||||
<span :class="mappedStatus.textClass">{{ mappedStatus.text }}</span>
|
||||
</div>
|
||||
</template>
|
|
@ -0,0 +1,55 @@
|
|||
<script>
|
||||
import ImportStatus from './import_status.vue';
|
||||
import { STATUSES } from '../constants';
|
||||
|
||||
export default {
|
||||
name: 'ImportedProjectTableRow',
|
||||
components: {
|
||||
ImportStatus,
|
||||
},
|
||||
props: {
|
||||
project: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
|
||||
computed: {
|
||||
displayFullPath() {
|
||||
return this.project.fullPath.replace(/^\//, '');
|
||||
},
|
||||
|
||||
isFinished() {
|
||||
return this.project.importStatus === STATUSES.FINISHED;
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<tr class="js-imported-project import-row">
|
||||
<td>
|
||||
<a
|
||||
:href="project.providerLink"
|
||||
rel="noreferrer noopener"
|
||||
target="_blank"
|
||||
class="js-provider-link"
|
||||
>
|
||||
{{ project.importSource }}
|
||||
</a>
|
||||
</td>
|
||||
<td class="js-full-path">{{ displayFullPath }}</td>
|
||||
<td><import-status :status="project.importStatus" /></td>
|
||||
<td>
|
||||
<a
|
||||
v-if="isFinished"
|
||||
class="btn btn-default js-go-to-project"
|
||||
:href="project.fullPath"
|
||||
rel="noreferrer noopener"
|
||||
target="_blank"
|
||||
>
|
||||
{{ __('Go to project') }}
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
</template>
|
|
@ -0,0 +1,110 @@
|
|||
<script>
|
||||
import { mapState, mapGetters, mapActions } from 'vuex';
|
||||
import Select2Select from '~/vue_shared/components/select2_select.vue';
|
||||
import { __ } from '~/locale';
|
||||
import LoadingButton from '~/vue_shared/components/loading_button.vue';
|
||||
import eventHub from '../event_hub';
|
||||
import { STATUSES } from '../constants';
|
||||
import ImportStatus from './import_status.vue';
|
||||
|
||||
export default {
|
||||
name: 'ProviderRepoTableRow',
|
||||
components: {
|
||||
Select2Select,
|
||||
LoadingButton,
|
||||
ImportStatus,
|
||||
},
|
||||
props: {
|
||||
repo: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
targetNamespace: this.$store.state.defaultTargetNamespace,
|
||||
newName: this.repo.sanitizedName,
|
||||
};
|
||||
},
|
||||
|
||||
computed: {
|
||||
...mapState(['namespaces', 'reposBeingImported', 'ciCdOnly']),
|
||||
|
||||
...mapGetters(['namespaceSelectOptions']),
|
||||
|
||||
importButtonText() {
|
||||
return this.ciCdOnly ? __('Connect') : __('Import');
|
||||
},
|
||||
|
||||
select2Options() {
|
||||
return {
|
||||
data: this.namespaceSelectOptions,
|
||||
containerCssClass:
|
||||
'import-namespace-select js-namespace-select qa-project-namespace-select',
|
||||
};
|
||||
},
|
||||
|
||||
isLoadingImport() {
|
||||
return this.reposBeingImported.includes(this.repo.id);
|
||||
},
|
||||
|
||||
status() {
|
||||
return this.isLoadingImport ? STATUSES.SCHEDULING : STATUSES.NONE;
|
||||
},
|
||||
},
|
||||
|
||||
created() {
|
||||
eventHub.$on('importAll', () => this.importRepo());
|
||||
},
|
||||
|
||||
methods: {
|
||||
...mapActions(['fetchImport']),
|
||||
|
||||
importRepo() {
|
||||
return this.fetchImport({
|
||||
newName: this.newName,
|
||||
targetNamespace: this.targetNamespace,
|
||||
repo: this.repo,
|
||||
});
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<tr class="qa-project-import-row js-provider-repo import-row">
|
||||
<td>
|
||||
<a
|
||||
:href="repo.providerLink"
|
||||
rel="noreferrer noopener"
|
||||
target="_blank"
|
||||
class="js-provider-link"
|
||||
>
|
||||
{{ repo.fullName }}
|
||||
</a>
|
||||
</td>
|
||||
<td class="d-flex flex-wrap flex-lg-nowrap">
|
||||
<select2-select v-model="targetNamespace" :options="select2Options" />
|
||||
<span class="px-2 import-slash-divider d-flex justify-content-center align-items-center"
|
||||
>/</span
|
||||
>
|
||||
<input
|
||||
v-model="newName"
|
||||
type="text"
|
||||
class="form-control import-project-name-input js-new-name qa-project-path-field"
|
||||
/>
|
||||
</td>
|
||||
<td><import-status :status="status" /></td>
|
||||
<td>
|
||||
<button
|
||||
v-if="!isLoadingImport"
|
||||
type="button"
|
||||
class="qa-import-button js-import-button btn btn-default"
|
||||
@click="importRepo"
|
||||
>
|
||||
{{ importButtonText }}
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
</template>
|
48
app/assets/javascripts/import_projects/constants.js
Normal file
48
app/assets/javascripts/import_projects/constants.js
Normal file
|
@ -0,0 +1,48 @@
|
|||
import { __ } from '../locale';
|
||||
|
||||
// The `scheduling` status is only present on the client-side,
|
||||
// it is used as the status when we are requesting to start an import.
|
||||
|
||||
export const STATUSES = {
|
||||
FINISHED: 'finished',
|
||||
FAILED: 'failed',
|
||||
SCHEDULED: 'scheduled',
|
||||
STARTED: 'started',
|
||||
NONE: 'none',
|
||||
SCHEDULING: 'scheduling',
|
||||
};
|
||||
|
||||
const STATUS_MAP = {
|
||||
[STATUSES.FINISHED]: {
|
||||
icon: 'success',
|
||||
text: __('Done'),
|
||||
textClass: 'text-success',
|
||||
},
|
||||
[STATUSES.FAILED]: {
|
||||
icon: 'failed',
|
||||
text: __('Failed'),
|
||||
textClass: 'text-danger',
|
||||
},
|
||||
[STATUSES.SCHEDULED]: {
|
||||
icon: 'pending',
|
||||
text: __('Scheduled'),
|
||||
textClass: 'text-warning',
|
||||
},
|
||||
[STATUSES.STARTED]: {
|
||||
icon: 'running',
|
||||
text: __('Running…'),
|
||||
textClass: 'text-info',
|
||||
},
|
||||
[STATUSES.NONE]: {
|
||||
icon: 'created',
|
||||
text: __('Not started'),
|
||||
textClass: 'text-muted',
|
||||
},
|
||||
[STATUSES.SCHEDULING]: {
|
||||
loadingIcon: true,
|
||||
text: __('Scheduling'),
|
||||
textClass: 'text-warning',
|
||||
},
|
||||
};
|
||||
|
||||
export default STATUS_MAP;
|
3
app/assets/javascripts/import_projects/event_hub.js
Normal file
3
app/assets/javascripts/import_projects/event_hub.js
Normal file
|
@ -0,0 +1,3 @@
|
|||
import Vue from 'vue';
|
||||
|
||||
export default new Vue();
|
47
app/assets/javascripts/import_projects/index.js
Normal file
47
app/assets/javascripts/import_projects/index.js
Normal file
|
@ -0,0 +1,47 @@
|
|||
import Vue from 'vue';
|
||||
import { mapActions } from 'vuex';
|
||||
import Translate from '../vue_shared/translate';
|
||||
import ImportProjectsTable from './components/import_projects_table.vue';
|
||||
import { parseBoolean } from '../lib/utils/common_utils';
|
||||
import store from './store';
|
||||
|
||||
Vue.use(Translate);
|
||||
|
||||
export default function mountImportProjectsTable(mountElement) {
|
||||
if (!mountElement) return undefined;
|
||||
|
||||
const {
|
||||
reposPath,
|
||||
provider,
|
||||
providerTitle,
|
||||
canSelectNamespace,
|
||||
jobsPath,
|
||||
importPath,
|
||||
ciCdOnly,
|
||||
} = mountElement.dataset;
|
||||
|
||||
return new Vue({
|
||||
el: mountElement,
|
||||
store,
|
||||
|
||||
created() {
|
||||
this.setInitialData({
|
||||
reposPath,
|
||||
provider,
|
||||
jobsPath,
|
||||
importPath,
|
||||
defaultTargetNamespace: gon.current_username,
|
||||
ciCdOnly: parseBoolean(ciCdOnly),
|
||||
canSelectNamespace: parseBoolean(canSelectNamespace),
|
||||
});
|
||||
},
|
||||
|
||||
methods: {
|
||||
...mapActions(['setInitialData']),
|
||||
},
|
||||
|
||||
render(createElement) {
|
||||
return createElement(ImportProjectsTable, { props: { providerTitle } });
|
||||
},
|
||||
});
|
||||
}
|
106
app/assets/javascripts/import_projects/store/actions.js
Normal file
106
app/assets/javascripts/import_projects/store/actions.js
Normal file
|
@ -0,0 +1,106 @@
|
|||
import Visibility from 'visibilityjs';
|
||||
import * as types from './mutation_types';
|
||||
import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils';
|
||||
import Poll from '~/lib/utils/poll';
|
||||
import createFlash from '~/flash';
|
||||
import { s__, sprintf } from '~/locale';
|
||||
import axios from '~/lib/utils/axios_utils';
|
||||
|
||||
let eTagPoll;
|
||||
|
||||
export const clearJobsEtagPoll = () => {
|
||||
eTagPoll = null;
|
||||
};
|
||||
export const stopJobsPolling = () => {
|
||||
if (eTagPoll) eTagPoll.stop();
|
||||
};
|
||||
export const restartJobsPolling = () => {
|
||||
if (eTagPoll) eTagPoll.restart();
|
||||
};
|
||||
|
||||
export const setInitialData = ({ commit }, data) => commit(types.SET_INITIAL_DATA, data);
|
||||
|
||||
export const requestRepos = ({ commit }, repos) => commit(types.REQUEST_REPOS, repos);
|
||||
export const receiveReposSuccess = ({ commit }, repos) =>
|
||||
commit(types.RECEIVE_REPOS_SUCCESS, repos);
|
||||
export const receiveReposError = ({ commit }) => commit(types.RECEIVE_REPOS_ERROR);
|
||||
export const fetchRepos = ({ state, dispatch }) => {
|
||||
dispatch('requestRepos');
|
||||
|
||||
return axios
|
||||
.get(state.reposPath)
|
||||
.then(({ data }) =>
|
||||
dispatch('receiveReposSuccess', convertObjectPropsToCamelCase(data, { deep: true })),
|
||||
)
|
||||
.then(() => dispatch('fetchJobs'))
|
||||
.catch(() => {
|
||||
createFlash(
|
||||
sprintf(s__('ImportProjects|Requesting your %{provider} repositories failed'), {
|
||||
provider: state.provider,
|
||||
}),
|
||||
);
|
||||
|
||||
dispatch('receiveReposError');
|
||||
});
|
||||
};
|
||||
|
||||
export const requestImport = ({ commit, state }, repoId) => {
|
||||
if (!state.reposBeingImported.includes(repoId)) commit(types.REQUEST_IMPORT, repoId);
|
||||
};
|
||||
export const receiveImportSuccess = ({ commit }, { importedProject, repoId }) =>
|
||||
commit(types.RECEIVE_IMPORT_SUCCESS, { importedProject, repoId });
|
||||
export const receiveImportError = ({ commit }, repoId) =>
|
||||
commit(types.RECEIVE_IMPORT_ERROR, repoId);
|
||||
export const fetchImport = ({ state, dispatch }, { newName, targetNamespace, repo }) => {
|
||||
dispatch('requestImport', repo.id);
|
||||
|
||||
return axios
|
||||
.post(state.importPath, {
|
||||
ci_cd_only: state.ciCdOnly,
|
||||
new_name: newName,
|
||||
repo_id: repo.id,
|
||||
target_namespace: targetNamespace,
|
||||
})
|
||||
.then(({ data }) =>
|
||||
dispatch('receiveImportSuccess', {
|
||||
importedProject: convertObjectPropsToCamelCase(data, { deep: true }),
|
||||
repoId: repo.id,
|
||||
}),
|
||||
)
|
||||
.catch(() => {
|
||||
createFlash(s__('ImportProjects|Importing the project failed'));
|
||||
|
||||
dispatch('receiveImportError', { repoId: repo.id });
|
||||
});
|
||||
};
|
||||
|
||||
export const receiveJobsSuccess = ({ commit }, updatedProjects) =>
|
||||
commit(types.RECEIVE_JOBS_SUCCESS, updatedProjects);
|
||||
export const fetchJobs = ({ state, dispatch }) => {
|
||||
if (eTagPoll) return;
|
||||
|
||||
eTagPoll = new Poll({
|
||||
resource: {
|
||||
fetchJobs: () => axios.get(state.jobsPath),
|
||||
},
|
||||
method: 'fetchJobs',
|
||||
successCallback: ({ data }) =>
|
||||
dispatch('receiveJobsSuccess', convertObjectPropsToCamelCase(data, { deep: true })),
|
||||
errorCallback: () => createFlash(s__('ImportProjects|Updating the imported projects failed')),
|
||||
});
|
||||
|
||||
if (!Visibility.hidden()) {
|
||||
eTagPoll.makeRequest();
|
||||
}
|
||||
|
||||
Visibility.change(() => {
|
||||
if (!Visibility.hidden()) {
|
||||
dispatch('restartJobsPolling');
|
||||
} else {
|
||||
dispatch('stopJobsPolling');
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
// prevent babel-plugin-rewire from generating an invalid default during karma tests
|
||||
export default () => {};
|
20
app/assets/javascripts/import_projects/store/getters.js
Normal file
20
app/assets/javascripts/import_projects/store/getters.js
Normal file
|
@ -0,0 +1,20 @@
|
|||
export const namespaceSelectOptions = state => {
|
||||
const serializedNamespaces = state.namespaces.map(({ fullPath }) => ({
|
||||
id: fullPath,
|
||||
text: fullPath,
|
||||
}));
|
||||
|
||||
return [
|
||||
{ text: 'Groups', children: serializedNamespaces },
|
||||
{
|
||||
text: 'Users',
|
||||
children: [{ id: state.defaultTargetNamespace, text: state.defaultTargetNamespace }],
|
||||
},
|
||||
];
|
||||
};
|
||||
|
||||
export const isImportingAnyRepo = state => state.reposBeingImported.length > 0;
|
||||
|
||||
export const hasProviderRepos = state => state.providerRepos.length > 0;
|
||||
|
||||
export const hasImportedProjects = state => state.importedProjects.length > 0;
|
15
app/assets/javascripts/import_projects/store/index.js
Normal file
15
app/assets/javascripts/import_projects/store/index.js
Normal file
|
@ -0,0 +1,15 @@
|
|||
import Vue from 'vue';
|
||||
import Vuex from 'vuex';
|
||||
import state from './state';
|
||||
import * as actions from './actions';
|
||||
import * as getters from './getters';
|
||||
import mutations from './mutations';
|
||||
|
||||
Vue.use(Vuex);
|
||||
|
||||
export default new Vuex.Store({
|
||||
state: state(),
|
||||
actions,
|
||||
mutations,
|
||||
getters,
|
||||
});
|
|
@ -0,0 +1,11 @@
|
|||
export const SET_INITIAL_DATA = 'SET_INITIAL_DATA';
|
||||
|
||||
export const REQUEST_REPOS = 'REQUEST_REPOS';
|
||||
export const RECEIVE_REPOS_SUCCESS = 'RECEIVE_REPOS_SUCCESS';
|
||||
export const RECEIVE_REPOS_ERROR = 'RECEIVE_REPOS_ERROR';
|
||||
|
||||
export const REQUEST_IMPORT = 'REQUEST_IMPORT';
|
||||
export const RECEIVE_IMPORT_SUCCESS = 'RECEIVE_IMPORT_SUCCESS';
|
||||
export const RECEIVE_IMPORT_ERROR = 'RECEIVE_IMPORT_ERROR';
|
||||
|
||||
export const RECEIVE_JOBS_SUCCESS = 'RECEIVE_JOBS_SUCCESS';
|
55
app/assets/javascripts/import_projects/store/mutations.js
Normal file
55
app/assets/javascripts/import_projects/store/mutations.js
Normal file
|
@ -0,0 +1,55 @@
|
|||
import Vue from 'vue';
|
||||
import * as types from './mutation_types';
|
||||
|
||||
export default {
|
||||
[types.SET_INITIAL_DATA](state, data) {
|
||||
Object.assign(state, data);
|
||||
},
|
||||
|
||||
[types.REQUEST_REPOS](state) {
|
||||
state.isLoadingRepos = true;
|
||||
},
|
||||
|
||||
[types.RECEIVE_REPOS_SUCCESS](state, { importedProjects, providerRepos, namespaces }) {
|
||||
state.isLoadingRepos = false;
|
||||
|
||||
state.importedProjects = importedProjects;
|
||||
state.providerRepos = providerRepos;
|
||||
state.namespaces = namespaces;
|
||||
},
|
||||
|
||||
[types.RECEIVE_REPOS_ERROR](state) {
|
||||
state.isLoadingRepos = false;
|
||||
},
|
||||
|
||||
[types.REQUEST_IMPORT](state, repoId) {
|
||||
state.reposBeingImported.push(repoId);
|
||||
},
|
||||
|
||||
[types.RECEIVE_IMPORT_SUCCESS](state, { importedProject, repoId }) {
|
||||
const existingRepoIndex = state.reposBeingImported.indexOf(repoId);
|
||||
if (state.reposBeingImported.includes(repoId))
|
||||
state.reposBeingImported.splice(existingRepoIndex, 1);
|
||||
|
||||
const providerRepoIndex = state.providerRepos.findIndex(
|
||||
providerRepo => providerRepo.id === repoId,
|
||||
);
|
||||
state.providerRepos.splice(providerRepoIndex, 1);
|
||||
state.importedProjects.unshift(importedProject);
|
||||
},
|
||||
|
||||
[types.RECEIVE_IMPORT_ERROR](state, repoId) {
|
||||
const repoIndex = state.reposBeingImported.indexOf(repoId);
|
||||
if (state.reposBeingImported.includes(repoId)) state.reposBeingImported.splice(repoIndex, 1);
|
||||
},
|
||||
|
||||
[types.RECEIVE_JOBS_SUCCESS](state, updatedProjects) {
|
||||
updatedProjects.forEach(updatedProject => {
|
||||
const existingProject = state.importedProjects.find(
|
||||
importedProject => importedProject.id === updatedProject.id,
|
||||
);
|
||||
|
||||
Vue.set(existingProject, 'importStatus', updatedProject.importStatus);
|
||||
});
|
||||
},
|
||||
};
|
15
app/assets/javascripts/import_projects/store/state.js
Normal file
15
app/assets/javascripts/import_projects/store/state.js
Normal file
|
@ -0,0 +1,15 @@
|
|||
export default () => ({
|
||||
reposPath: '',
|
||||
importPath: '',
|
||||
jobsPath: '',
|
||||
currentProjectId: '',
|
||||
provider: '',
|
||||
currentUsername: '',
|
||||
importedProjects: [],
|
||||
providerRepos: [],
|
||||
namespaces: [],
|
||||
reposBeingImported: [],
|
||||
isLoadingRepos: false,
|
||||
canSelectNamespace: false,
|
||||
ciCdOnly: false,
|
||||
});
|
|
@ -110,7 +110,7 @@ export default {
|
|||
<div class="sidebar-container">
|
||||
<div class="blocks-container">
|
||||
<div class="block d-flex flex-nowrap align-items-center">
|
||||
<h4 class="my-0 mr-2">{{ job.name }}</h4>
|
||||
<h4 class="my-0 mr-2 text-break-word">{{ job.name }}</h4>
|
||||
<div class="flex-grow-1 flex-shrink-0 text-right">
|
||||
<gl-link
|
||||
v-if="job.retry_path"
|
||||
|
|
|
@ -130,7 +130,7 @@ export const isInViewport = (el, offset = {}) => {
|
|||
rect.top >= (top || 0) &&
|
||||
rect.left >= (left || 0) &&
|
||||
rect.bottom <= window.innerHeight &&
|
||||
rect.right <= window.innerWidth
|
||||
parseInt(rect.right, 10) <= window.innerWidth
|
||||
);
|
||||
};
|
||||
|
||||
|
|
|
@ -5,6 +5,7 @@ import DiffViewer from '~/vue_shared/components/diff_viewer/diff_viewer.vue';
|
|||
import ImageDiffOverlay from '~/diffs/components/image_diff_overlay.vue';
|
||||
import { GlSkeletonLoading } from '@gitlab/ui';
|
||||
import { getDiffMode } from '~/diffs/store/utils';
|
||||
import { diffViewerModes } from '~/ide/constants';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
|
@ -31,6 +32,12 @@ export default {
|
|||
diffMode() {
|
||||
return getDiffMode(this.discussion.diff_file);
|
||||
},
|
||||
diffViewerMode() {
|
||||
return this.discussion.diff_file.viewer.name;
|
||||
},
|
||||
isTextFile() {
|
||||
return this.diffViewerMode === diffViewerModes.text;
|
||||
},
|
||||
hasTruncatedDiffLines() {
|
||||
return (
|
||||
this.discussion.truncated_diff_lines && this.discussion.truncated_diff_lines.length !== 0
|
||||
|
@ -58,18 +65,14 @@ export default {
|
|||
</script>
|
||||
|
||||
<template>
|
||||
<div :class="{ 'text-file': discussion.diff_file.text }" class="diff-file file-holder">
|
||||
<div :class="{ 'text-file': isTextFile }" class="diff-file file-holder">
|
||||
<diff-file-header
|
||||
:discussion-path="discussion.discussion_path"
|
||||
:diff-file="discussion.diff_file"
|
||||
:can-current-user-fork="false"
|
||||
:expanded="!discussion.diff_file.collapsed"
|
||||
:expanded="!discussion.diff_file.viewer.collapsed"
|
||||
/>
|
||||
<div
|
||||
v-if="discussion.diff_file.text"
|
||||
:class="$options.userColorSchemeClass"
|
||||
class="diff-content code"
|
||||
>
|
||||
<div v-if="isTextFile" :class="$options.userColorSchemeClass" class="diff-content code">
|
||||
<table>
|
||||
<template v-if="hasTruncatedDiffLines">
|
||||
<tr
|
||||
|
@ -109,6 +112,7 @@ export default {
|
|||
<div v-else>
|
||||
<diff-viewer
|
||||
:diff-mode="diffMode"
|
||||
:diff-viewer-mode="diffViewerMode"
|
||||
:new-path="discussion.diff_file.new_path"
|
||||
:new-sha="discussion.diff_file.diff_refs.head_sha"
|
||||
:old-path="discussion.diff_file.old_path"
|
||||
|
|
|
@ -23,11 +23,6 @@ export default {
|
|||
type: [String, Number],
|
||||
required: true,
|
||||
},
|
||||
discussionId: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: '',
|
||||
},
|
||||
noteUrl: {
|
||||
type: String,
|
||||
required: false,
|
||||
|
@ -126,6 +121,11 @@ export default {
|
|||
onResolve() {
|
||||
this.$emit('handleResolve');
|
||||
},
|
||||
closeTooltip() {
|
||||
this.$nextTick(() => {
|
||||
this.$root.$emit('bv::hide::tooltip');
|
||||
});
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
@ -171,7 +171,7 @@ export default {
|
|||
v-if="showReplyButton"
|
||||
ref="replyButton"
|
||||
class="js-reply-button"
|
||||
:note-id="discussionId"
|
||||
@startReplying="$emit('startReplying')"
|
||||
/>
|
||||
<div v-if="canEdit" class="note-actions-item">
|
||||
<button
|
||||
|
@ -202,6 +202,7 @@ export default {
|
|||
title="More actions"
|
||||
class="note-action-button more-actions-toggle btn btn-transparent"
|
||||
data-toggle="dropdown"
|
||||
@click="closeTooltip"
|
||||
>
|
||||
<icon css-classes="icon" name="ellipsis_v" />
|
||||
</button>
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
<script>
|
||||
import { mapActions } from 'vuex';
|
||||
import { GlTooltipDirective, GlButton } from '@gitlab/ui';
|
||||
import Icon from '~/vue_shared/components/icon.vue';
|
||||
|
||||
|
@ -12,15 +11,6 @@ export default {
|
|||
directives: {
|
||||
GlTooltip: GlTooltipDirective,
|
||||
},
|
||||
props: {
|
||||
noteId: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
...mapActions(['convertToDiscussion']),
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
|
@ -32,7 +22,7 @@ export default {
|
|||
class="note-action-button"
|
||||
variant="transparent"
|
||||
:title="__('Reply to comment')"
|
||||
@click="convertToDiscussion(noteId)"
|
||||
@click="$emit('startReplying')"
|
||||
>
|
||||
<icon name="comment" css-classes="link-highlight" />
|
||||
</gl-button>
|
||||
|
|
|
@ -95,6 +95,7 @@ export default {
|
|||
<div ref="note-body" :class="{ 'js-task-list-container': canEdit }" class="note-body">
|
||||
<suggestions
|
||||
v-if="hasSuggestion && !isEditing"
|
||||
class="note-text md"
|
||||
:suggestions="note.suggestions"
|
||||
:note-html="note.note_html"
|
||||
:line-type="lineType"
|
||||
|
|
|
@ -26,6 +26,7 @@ import resolvable from '../mixins/resolvable';
|
|||
import discussionNavigation from '../mixins/discussion_navigation';
|
||||
import ReplyPlaceholder from './discussion_reply_placeholder.vue';
|
||||
import jumpToNextDiscussionButton from './discussion_jump_to_next_button.vue';
|
||||
import eventHub from '../event_hub';
|
||||
|
||||
export default {
|
||||
name: 'NoteableDiscussion',
|
||||
|
@ -93,6 +94,7 @@ export default {
|
|||
},
|
||||
computed: {
|
||||
...mapGetters([
|
||||
'convertedDisscussionIds',
|
||||
'getNoteableData',
|
||||
'nextUnresolvedDiscussionId',
|
||||
'unresolvedDiscussionsCount',
|
||||
|
@ -245,6 +247,12 @@ export default {
|
|||
}
|
||||
},
|
||||
},
|
||||
created() {
|
||||
eventHub.$on('startReplying', this.onStartReplying);
|
||||
},
|
||||
beforeDestroy() {
|
||||
eventHub.$off('startReplying', this.onStartReplying);
|
||||
},
|
||||
methods: {
|
||||
...mapActions([
|
||||
'saveNote',
|
||||
|
@ -252,6 +260,7 @@ export default {
|
|||
'removePlaceholderNotes',
|
||||
'toggleResolveNote',
|
||||
'expandDiscussion',
|
||||
'removeConvertedDiscussion',
|
||||
]),
|
||||
truncateSha,
|
||||
componentName(note) {
|
||||
|
@ -291,6 +300,10 @@ export default {
|
|||
}
|
||||
}
|
||||
|
||||
if (this.convertedDisscussionIds.includes(this.discussion.id)) {
|
||||
this.removeConvertedDiscussion(this.discussion.id);
|
||||
}
|
||||
|
||||
this.isReplying = false;
|
||||
this.resetAutoSave();
|
||||
},
|
||||
|
@ -301,6 +314,10 @@ export default {
|
|||
note: { note: noteText },
|
||||
};
|
||||
|
||||
if (this.convertedDisscussionIds.includes(this.discussion.id)) {
|
||||
postData.return_discussion = true;
|
||||
}
|
||||
|
||||
if (this.discussion.for_commit) {
|
||||
postData.note_project_id = this.discussion.project_id;
|
||||
}
|
||||
|
@ -340,6 +357,11 @@ Please check your network connection and try again.`;
|
|||
deleteNoteHandler(note) {
|
||||
this.$emit('noteDeleted', this.discussion, note);
|
||||
},
|
||||
onStartReplying(discussionId) {
|
||||
if (this.discussion.id === discussionId) {
|
||||
this.showReplyForm();
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
@ -358,30 +380,32 @@ Please check your network connection and try again.`;
|
|||
:img-size="40"
|
||||
/>
|
||||
</div>
|
||||
<note-header
|
||||
:author="author"
|
||||
:created-at="initialDiscussion.created_at"
|
||||
:note-id="initialDiscussion.id"
|
||||
:include-toggle="true"
|
||||
:expanded="discussion.expanded"
|
||||
@toggleHandler="toggleDiscussionHandler"
|
||||
>
|
||||
<span v-html="actionText"></span>
|
||||
</note-header>
|
||||
<note-edited-text
|
||||
v-if="discussion.resolved"
|
||||
:edited-at="discussion.resolved_at"
|
||||
:edited-by="discussion.resolved_by"
|
||||
:action-text="resolvedText"
|
||||
class-name="discussion-headline-light js-discussion-headline"
|
||||
/>
|
||||
<note-edited-text
|
||||
v-else-if="lastUpdatedAt"
|
||||
:edited-at="lastUpdatedAt"
|
||||
:edited-by="lastUpdatedBy"
|
||||
action-text="Last updated"
|
||||
class-name="discussion-headline-light js-discussion-headline"
|
||||
/>
|
||||
<div class="timeline-content">
|
||||
<note-header
|
||||
:author="author"
|
||||
:created-at="initialDiscussion.created_at"
|
||||
:note-id="initialDiscussion.id"
|
||||
:include-toggle="true"
|
||||
:expanded="discussion.expanded"
|
||||
@toggleHandler="toggleDiscussionHandler"
|
||||
>
|
||||
<span v-html="actionText"></span>
|
||||
</note-header>
|
||||
<note-edited-text
|
||||
v-if="discussion.resolved"
|
||||
:edited-at="discussion.resolved_at"
|
||||
:edited-by="discussion.resolved_by"
|
||||
:action-text="resolvedText"
|
||||
class-name="discussion-headline-light js-discussion-headline"
|
||||
/>
|
||||
<note-edited-text
|
||||
v-else-if="lastUpdatedAt"
|
||||
:edited-at="lastUpdatedAt"
|
||||
:edited-by="lastUpdatedBy"
|
||||
action-text="Last updated"
|
||||
class-name="discussion-headline-light js-discussion-headline"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="shouldShowDiscussions" class="discussion-body">
|
||||
<component
|
||||
|
@ -400,6 +424,7 @@ Please check your network connection and try again.`;
|
|||
:help-page-path="helpPagePath"
|
||||
:show-reply-button="canReply"
|
||||
@handleDeleteNote="deleteNoteHandler"
|
||||
@startReplying="showReplyForm"
|
||||
>
|
||||
<note-edited-text
|
||||
v-if="discussion.resolved"
|
||||
|
|
|
@ -29,11 +29,6 @@ export default {
|
|||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
discussion: {
|
||||
type: Object,
|
||||
required: false,
|
||||
default: null,
|
||||
},
|
||||
line: {
|
||||
type: Object,
|
||||
required: false,
|
||||
|
@ -49,6 +44,11 @@ export default {
|
|||
required: false,
|
||||
default: () => null,
|
||||
},
|
||||
showReplyButton: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
|
@ -91,13 +91,6 @@ export default {
|
|||
}
|
||||
return '';
|
||||
},
|
||||
showReplyButton() {
|
||||
if (!this.discussion || !this.getNoteableData.current_user.can_create_note) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return this.discussion.individual_note && !this.commentsDisabled;
|
||||
},
|
||||
actionText() {
|
||||
if (!this.commit) {
|
||||
return '';
|
||||
|
@ -260,10 +253,10 @@ export default {
|
|||
:is-resolved="note.resolved"
|
||||
:is-resolving="isResolving"
|
||||
:resolved-by="note.resolved_by"
|
||||
:discussion-id="discussionId"
|
||||
@handleEdit="editHandler"
|
||||
@handleDelete="deleteHandler"
|
||||
@handleResolve="resolveHandler"
|
||||
@startReplying="$emit('startReplying')"
|
||||
/>
|
||||
</div>
|
||||
<div class="timeline-discussion-body">
|
||||
|
|
|
@ -60,9 +60,11 @@ export default {
|
|||
...mapGetters([
|
||||
'isNotesFetched',
|
||||
'discussions',
|
||||
'convertedDisscussionIds',
|
||||
'getNotesDataByProp',
|
||||
'isLoading',
|
||||
'commentsDisabled',
|
||||
'getNoteableData',
|
||||
]),
|
||||
noteableType() {
|
||||
return this.noteableData.noteableType;
|
||||
|
@ -78,6 +80,9 @@ export default {
|
|||
|
||||
return this.discussions;
|
||||
},
|
||||
canReply() {
|
||||
return this.getNoteableData.current_user.can_create_note && !this.commentsDisabled;
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
shouldShow() {
|
||||
|
@ -128,6 +133,7 @@ export default {
|
|||
'setNotesFetchedState',
|
||||
'expandDiscussion',
|
||||
'startTaskList',
|
||||
'convertToDiscussion',
|
||||
]),
|
||||
fetchNotes() {
|
||||
if (this.isFetching) return null;
|
||||
|
@ -175,6 +181,11 @@ export default {
|
|||
}
|
||||
}
|
||||
},
|
||||
startReplying(discussionId) {
|
||||
return this.convertToDiscussion(discussionId)
|
||||
.then(() => this.$nextTick())
|
||||
.then(() => eventHub.$emit('startReplying', discussionId));
|
||||
},
|
||||
},
|
||||
systemNote: constants.SYSTEM_NOTE,
|
||||
};
|
||||
|
@ -193,7 +204,9 @@ export default {
|
|||
/>
|
||||
<placeholder-note v-else :key="discussion.id" :note="discussion.notes[0]" />
|
||||
</template>
|
||||
<template v-else-if="discussion.individual_note">
|
||||
<template
|
||||
v-else-if="discussion.individual_note && !convertedDisscussionIds.includes(discussion.id)"
|
||||
>
|
||||
<system-note
|
||||
v-if="discussion.notes[0].system"
|
||||
:key="discussion.id"
|
||||
|
@ -203,7 +216,8 @@ export default {
|
|||
v-else
|
||||
:key="discussion.id"
|
||||
:note="discussion.notes[0]"
|
||||
:discussion="discussion"
|
||||
:show-reply-button="canReply"
|
||||
@startReplying="startReplying(discussion.id)"
|
||||
/>
|
||||
</template>
|
||||
<noteable-discussion
|
||||
|
|
|
@ -83,12 +83,44 @@ export const updateNote = ({ commit, dispatch }, { endpoint, note }) =>
|
|||
dispatch('startTaskList');
|
||||
});
|
||||
|
||||
export const replyToDiscussion = ({ commit }, { endpoint, data }) =>
|
||||
export const updateOrCreateNotes = ({ commit, state, getters, dispatch }, notes) => {
|
||||
const { notesById } = getters;
|
||||
|
||||
notes.forEach(note => {
|
||||
if (notesById[note.id]) {
|
||||
commit(types.UPDATE_NOTE, note);
|
||||
} else if (note.type === constants.DISCUSSION_NOTE || note.type === constants.DIFF_NOTE) {
|
||||
const discussion = utils.findNoteObjectById(state.discussions, note.discussion_id);
|
||||
|
||||
if (discussion) {
|
||||
commit(types.ADD_NEW_REPLY_TO_DISCUSSION, note);
|
||||
} else if (note.type === constants.DIFF_NOTE) {
|
||||
dispatch('fetchDiscussions', { path: state.notesData.discussionsPath });
|
||||
} else {
|
||||
commit(types.ADD_NEW_NOTE, note);
|
||||
}
|
||||
} else {
|
||||
commit(types.ADD_NEW_NOTE, note);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
export const replyToDiscussion = ({ commit, state, getters, dispatch }, { endpoint, data }) =>
|
||||
service
|
||||
.replyToDiscussion(endpoint, data)
|
||||
.then(res => res.json())
|
||||
.then(res => {
|
||||
commit(types.ADD_NEW_REPLY_TO_DISCUSSION, res);
|
||||
if (res.discussion) {
|
||||
commit(types.UPDATE_DISCUSSION, res.discussion);
|
||||
|
||||
updateOrCreateNotes({ commit, state, getters, dispatch }, res.discussion.notes);
|
||||
|
||||
dispatch('updateMergeRequestWidget');
|
||||
dispatch('startTaskList');
|
||||
dispatch('updateResolvableDiscussonsCounts');
|
||||
} else {
|
||||
commit(types.ADD_NEW_REPLY_TO_DISCUSSION, res);
|
||||
}
|
||||
|
||||
return res;
|
||||
});
|
||||
|
@ -262,25 +294,7 @@ export const saveNote = ({ commit, dispatch }, noteData) => {
|
|||
|
||||
const pollSuccessCallBack = (resp, commit, state, getters, dispatch) => {
|
||||
if (resp.notes && resp.notes.length) {
|
||||
const { notesById } = getters;
|
||||
|
||||
resp.notes.forEach(note => {
|
||||
if (notesById[note.id]) {
|
||||
commit(types.UPDATE_NOTE, note);
|
||||
} else if (note.type === constants.DISCUSSION_NOTE || note.type === constants.DIFF_NOTE) {
|
||||
const discussion = utils.findNoteObjectById(state.discussions, note.discussion_id);
|
||||
|
||||
if (discussion) {
|
||||
commit(types.ADD_NEW_REPLY_TO_DISCUSSION, note);
|
||||
} else if (note.type === constants.DIFF_NOTE) {
|
||||
dispatch('fetchDiscussions', { path: state.notesData.discussionsPath });
|
||||
} else {
|
||||
commit(types.ADD_NEW_NOTE, note);
|
||||
}
|
||||
} else {
|
||||
commit(types.ADD_NEW_NOTE, note);
|
||||
}
|
||||
});
|
||||
updateOrCreateNotes({ commit, state, getters, dispatch }, resp.notes);
|
||||
|
||||
dispatch('startTaskList');
|
||||
}
|
||||
|
@ -429,5 +443,8 @@ export const submitSuggestion = (
|
|||
export const convertToDiscussion = ({ commit }, noteId) =>
|
||||
commit(types.CONVERT_TO_DISCUSSION, noteId);
|
||||
|
||||
export const removeConvertedDiscussion = ({ commit }, noteId) =>
|
||||
commit(types.REMOVE_CONVERTED_DISCUSSION, noteId);
|
||||
|
||||
// prevent babel-plugin-rewire from generating an invalid default during karma tests
|
||||
export default () => {};
|
||||
|
|
|
@ -4,6 +4,8 @@ import { collapseSystemNotes } from './collapse_utils';
|
|||
|
||||
export const discussions = state => collapseSystemNotes(state.discussions);
|
||||
|
||||
export const convertedDisscussionIds = state => state.convertedDisscussionIds;
|
||||
|
||||
export const targetNoteHash = state => state.targetNoteHash;
|
||||
|
||||
export const getNotesData = state => state.notesData;
|
||||
|
|
|
@ -5,6 +5,7 @@ import mutations from '../mutations';
|
|||
export default () => ({
|
||||
state: {
|
||||
discussions: [],
|
||||
convertedDisscussionIds: [],
|
||||
targetNoteHash: null,
|
||||
lastFetchedAt: null,
|
||||
|
||||
|
|
|
@ -18,6 +18,7 @@ export const SET_NOTES_LOADING_STATE = 'SET_NOTES_LOADING_STATE';
|
|||
export const DISABLE_COMMENTS = 'DISABLE_COMMENTS';
|
||||
export const APPLY_SUGGESTION = 'APPLY_SUGGESTION';
|
||||
export const CONVERT_TO_DISCUSSION = 'CONVERT_TO_DISCUSSION';
|
||||
export const REMOVE_CONVERTED_DISCUSSION = 'REMOVE_CONVERTED_DISCUSSION';
|
||||
|
||||
// DISCUSSION
|
||||
export const COLLAPSE_DISCUSSION = 'COLLAPSE_DISCUSSION';
|
||||
|
|
|
@ -266,7 +266,14 @@ export default {
|
|||
},
|
||||
|
||||
[types.CONVERT_TO_DISCUSSION](state, discussionId) {
|
||||
const discussion = utils.findNoteObjectById(state.discussions, discussionId);
|
||||
Object.assign(discussion, { individual_note: false });
|
||||
const convertedDisscussionIds = [...state.convertedDisscussionIds, discussionId];
|
||||
Object.assign(state, { convertedDisscussionIds });
|
||||
},
|
||||
|
||||
[types.REMOVE_CONVERTED_DISCUSSION](state, discussionId) {
|
||||
const convertedDisscussionIds = [...state.convertedDisscussionIds];
|
||||
|
||||
convertedDisscussionIds.splice(convertedDisscussionIds.indexOf(discussionId), 1);
|
||||
Object.assign(state, { convertedDisscussionIds });
|
||||
},
|
||||
};
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
import mountImportProjectsTable from '~/import_projects';
|
||||
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
const mountElement = document.getElementById('import-projects-mount-element');
|
||||
|
||||
mountImportProjectsTable(mountElement);
|
||||
});
|
|
@ -0,0 +1,7 @@
|
|||
import mountImportProjectsTable from '~/import_projects';
|
||||
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
const mountElement = document.getElementById('import-projects-mount-element');
|
||||
|
||||
mountImportProjectsTable(mountElement);
|
||||
});
|
|
@ -1,5 +1,6 @@
|
|||
import $ from 'jquery';
|
||||
import UsernameValidator from './username_validator';
|
||||
import NoEmojiValidator from '../../../emoji/no_emoji_validator';
|
||||
import SigninTabsMemoizer from './signin_tabs_memoizer';
|
||||
import OAuthRememberMe from './oauth_remember_me';
|
||||
import preserveUrlFragment from './preserve_url_fragment';
|
||||
|
@ -7,6 +8,7 @@ import preserveUrlFragment from './preserve_url_fragment';
|
|||
document.addEventListener('DOMContentLoaded', () => {
|
||||
new UsernameValidator(); // eslint-disable-line no-new
|
||||
new SigninTabsMemoizer(); // eslint-disable-line no-new
|
||||
new NoEmojiValidator(); // eslint-disable-line no-new
|
||||
|
||||
new OAuthRememberMe({
|
||||
container: $('.omniauth-container'),
|
||||
|
|
|
@ -59,17 +59,19 @@ export default {
|
|||
</script>
|
||||
<template>
|
||||
<div class="btn-group">
|
||||
<gl-button
|
||||
<button
|
||||
v-gl-tooltip
|
||||
type="button"
|
||||
:disabled="isLoading"
|
||||
class="dropdown-new btn btn-default js-pipeline-dropdown-manual-actions"
|
||||
title="Manual job"
|
||||
:title="__('Manual job')"
|
||||
data-toggle="dropdown"
|
||||
aria-label="Manual job"
|
||||
:aria-label="__('Manual job')"
|
||||
>
|
||||
<icon name="play" class="icon-play" /> <i class="fa fa-caret-down" aria-hidden="true"> </i>
|
||||
<icon name="play" class="icon-play" />
|
||||
<i class="fa fa-caret-down" aria-hidden="true"></i>
|
||||
<gl-loading-icon v-if="isLoading" />
|
||||
</gl-button>
|
||||
</button>
|
||||
|
||||
<ul class="dropdown-menu dropdown-menu-right">
|
||||
<li v-for="action in actions" :key="action.path">
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<script>
|
||||
import { GlLink, GlButton, GlTooltipDirective } from '@gitlab/ui';
|
||||
import { GlLink, GlTooltipDirective } from '@gitlab/ui';
|
||||
import Icon from '~/vue_shared/components/icon.vue';
|
||||
|
||||
export default {
|
||||
|
@ -9,7 +9,6 @@ export default {
|
|||
components: {
|
||||
Icon,
|
||||
GlLink,
|
||||
GlButton,
|
||||
},
|
||||
props: {
|
||||
artifacts: {
|
||||
|
@ -21,20 +20,22 @@ export default {
|
|||
</script>
|
||||
<template>
|
||||
<div class="btn-group" role="group">
|
||||
<gl-button
|
||||
<button
|
||||
v-gl-tooltip
|
||||
class="dropdown-toggle build-artifacts js-pipeline-dropdown-download"
|
||||
title="Artifacts"
|
||||
type="button"
|
||||
class="dropdown-toggle build-artifacts btn btn-default js-pipeline-dropdown-download"
|
||||
:title="__('Artifacts')"
|
||||
data-toggle="dropdown"
|
||||
aria-label="Artifacts"
|
||||
:aria-label="__('Artifacts')"
|
||||
>
|
||||
<icon name="download" /> <i class="fa fa-caret-down" aria-hidden="true"> </i>
|
||||
</gl-button>
|
||||
<icon name="download" />
|
||||
<i class="fa fa-caret-down" aria-hidden="true"></i>
|
||||
</button>
|
||||
<ul class="dropdown-menu dropdown-menu-right">
|
||||
<li v-for="(artifact, i) in artifacts" :key="i">
|
||||
<gl-link :href="artifact.path" rel="nofollow" download>
|
||||
Download {{ artifact.name }} artifacts
|
||||
</gl-link>
|
||||
<gl-link :href="artifact.path" rel="nofollow" download
|
||||
>Download {{ artifact.name }} artifacts</gl-link
|
||||
>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
|
|
@ -73,14 +73,14 @@ export default {
|
|||
<gl-button
|
||||
:aria-label="ariaLabel"
|
||||
variant="blank"
|
||||
class="commit-edit-toggle mr-2"
|
||||
class="commit-edit-toggle square s24 mr-2"
|
||||
@click.stop="toggle()"
|
||||
>
|
||||
<icon :name="collapseIcon" :size="16" />
|
||||
</gl-button>
|
||||
<span v-if="expanded">{{ __('Collapse') }}</span>
|
||||
<span v-else>
|
||||
<span v-html="message"></span>
|
||||
<span class="vertical-align-middle" v-html="message"></span>
|
||||
<gl-button variant="link" class="modify-message-button">
|
||||
{{ modifyLinkMessage }}
|
||||
</gl-button>
|
||||
|
|
|
@ -37,6 +37,11 @@ export default {
|
|||
required: false,
|
||||
default: 12,
|
||||
},
|
||||
isCentered: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: true,
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
changedIcon() {
|
||||
|
@ -78,7 +83,12 @@ export default {
|
|||
</script>
|
||||
|
||||
<template>
|
||||
<span v-gl-tooltip.right :title="tooltipTitle" class="file-changed-icon ml-auto">
|
||||
<span
|
||||
v-gl-tooltip.right
|
||||
:title="tooltipTitle"
|
||||
:class="{ 'ml-auto': isCentered }"
|
||||
class="file-changed-icon"
|
||||
>
|
||||
<icon v-if="showIcon" :name="changedIcon" :size="size" :css-classes="changedIconClass" />
|
||||
</span>
|
||||
</template>
|
||||
|
|
|
@ -46,6 +46,11 @@ export default {
|
|||
required: false,
|
||||
default: false,
|
||||
},
|
||||
cssClasses: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: '',
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
cssClass() {
|
||||
|
@ -59,5 +64,5 @@ export default {
|
|||
};
|
||||
</script>
|
||||
<template>
|
||||
<span :class="cssClass"> <icon :name="icon" :size="size" /> </span>
|
||||
<span :class="cssClass"> <icon :name="icon" :size="size" :css-classes="cssClasses" /> </span>
|
||||
</template>
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
<script>
|
||||
import { diffModes } from '~/ide/constants';
|
||||
import { viewerInformationForPath } from '../content_viewer/lib/viewer_utils';
|
||||
import { diffViewerModes, diffModes } from '~/ide/constants';
|
||||
import ImageDiffViewer from './viewers/image_diff_viewer.vue';
|
||||
import DownloadDiffViewer from './viewers/download_diff_viewer.vue';
|
||||
import RenamedFile from './viewers/renamed.vue';
|
||||
|
@ -12,6 +11,10 @@ export default {
|
|||
type: String,
|
||||
required: true,
|
||||
},
|
||||
diffViewerMode: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
newPath: {
|
||||
type: String,
|
||||
required: true,
|
||||
|
@ -46,7 +49,7 @@ export default {
|
|||
},
|
||||
computed: {
|
||||
viewer() {
|
||||
if (this.diffMode === diffModes.renamed) {
|
||||
if (this.diffViewerMode === diffViewerModes.renamed) {
|
||||
return RenamedFile;
|
||||
} else if (this.diffMode === diffModes.mode_changed) {
|
||||
return ModeChanged;
|
||||
|
@ -54,11 +57,8 @@ export default {
|
|||
|
||||
if (!this.newPath) return null;
|
||||
|
||||
const previewInfo = viewerInformationForPath(this.newPath);
|
||||
if (!previewInfo) return DownloadDiffViewer;
|
||||
|
||||
switch (previewInfo.id) {
|
||||
case 'image':
|
||||
switch (this.diffViewerMode) {
|
||||
case diffViewerModes.image:
|
||||
return ImageDiffViewer;
|
||||
default:
|
||||
return DownloadDiffViewer;
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
<template>
|
||||
<div class="nothing-here-block">
|
||||
{{ __('No preview for this file type') }}
|
||||
</div>
|
||||
</template>
|
|
@ -0,0 +1,5 @@
|
|||
<template>
|
||||
<div class="nothing-here-block">
|
||||
{{ __('This diff was suppressed by a .gitattributes entry.') }}
|
||||
</div>
|
||||
</template>
|
|
@ -136,6 +136,7 @@ export default {
|
|||
<div
|
||||
v-else
|
||||
:class="fileClass"
|
||||
:title="file.name"
|
||||
class="file-row"
|
||||
role="button"
|
||||
@click="clickFile"
|
||||
|
|
|
@ -130,6 +130,6 @@ export default {
|
|||
<template>
|
||||
<div>
|
||||
<div class="flash-container js-suggestions-flash"></div>
|
||||
<div v-show="isRendered" ref="container" class="note-text md" v-html="noteHtml"></div>
|
||||
<div v-show="isRendered" ref="container" v-html="noteHtml"></div>
|
||||
</div>
|
||||
</template>
|
||||
|
|
|
@ -28,11 +28,12 @@ export default {
|
|||
data() {
|
||||
return {
|
||||
size: this.startSize,
|
||||
isDragging: false,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
className() {
|
||||
return `drag-${this.side}`;
|
||||
return [`position-${this.side}-0`, { 'is-dragging': this.isDragging }];
|
||||
},
|
||||
cursorStyle() {
|
||||
if (this.enabled) {
|
||||
|
@ -57,6 +58,7 @@ export default {
|
|||
startDrag(e) {
|
||||
if (this.enabled) {
|
||||
e.preventDefault();
|
||||
this.isDragging = true;
|
||||
this.startPos = e.clientX;
|
||||
this.currentStartSize = this.size;
|
||||
document.addEventListener('mousemove', this.drag);
|
||||
|
@ -80,6 +82,7 @@ export default {
|
|||
},
|
||||
endDrag(e) {
|
||||
e.preventDefault();
|
||||
this.isDragging = false;
|
||||
document.removeEventListener('mousemove', this.drag);
|
||||
this.$emit('resize-end', this.size);
|
||||
},
|
||||
|
@ -91,7 +94,7 @@ export default {
|
|||
<div
|
||||
:class="className"
|
||||
:style="cursorStyle"
|
||||
class="drag-handle"
|
||||
class="position-absolute position-top-0 position-bottom-0 drag-handle"
|
||||
@mousedown="startDrag"
|
||||
@dblclick="resetSize"
|
||||
></div>
|
||||
|
|
|
@ -0,0 +1,34 @@
|
|||
<script>
|
||||
import $ from 'jquery';
|
||||
|
||||
export default {
|
||||
name: 'Select2Select',
|
||||
props: {
|
||||
options: {
|
||||
type: Object,
|
||||
required: false,
|
||||
default: () => ({}),
|
||||
},
|
||||
value: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: '',
|
||||
},
|
||||
},
|
||||
|
||||
mounted() {
|
||||
$(this.$refs.dropdownInput)
|
||||
.val(this.value)
|
||||
.select2(this.options)
|
||||
.on('change', event => this.$emit('input', event.target.value));
|
||||
},
|
||||
|
||||
beforeDestroy() {
|
||||
$(this.$refs.dropdownInput).select2('destroy');
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<input ref="dropdownInput" type="hidden" />
|
||||
</template>
|
|
@ -63,15 +63,15 @@
|
|||
//
|
||||
// Pass in any number of transitions
|
||||
@mixin transition($transitions...) {
|
||||
$unfoldedTransitions: ();
|
||||
$unfolded-transitions: ();
|
||||
@each $transition in $transitions {
|
||||
$unfoldedTransitions: append($unfoldedTransitions, unfoldTransition($transition), comma);
|
||||
$unfolded-transitions: append($unfolded-transitions, unfold-transition($transition), comma);
|
||||
}
|
||||
|
||||
transition: $unfoldedTransitions;
|
||||
transition: $unfolded-transitions;
|
||||
}
|
||||
|
||||
@mixin disableAllAnimation {
|
||||
@mixin disable-all-animation {
|
||||
/*CSS transitions*/
|
||||
-o-transition-property: none !important;
|
||||
-moz-transition-property: none !important;
|
||||
|
@ -92,27 +92,27 @@
|
|||
animation: none !important;
|
||||
}
|
||||
|
||||
@function unfoldTransition ($transition) {
|
||||
@function unfold-transition ($transition) {
|
||||
// Default values
|
||||
$property: all;
|
||||
$duration: $general-hover-transition-duration;
|
||||
$easing: $general-hover-transition-curve; // Browser default is ease, which is what we want
|
||||
$delay: null; // Browser default is 0, which is what we want
|
||||
$defaultProperties: ($property, $duration, $easing, $delay);
|
||||
$default-properties: ($property, $duration, $easing, $delay);
|
||||
|
||||
// Grab transition properties if they exist
|
||||
$unfoldedTransition: ();
|
||||
@for $i from 1 through length($defaultProperties) {
|
||||
$unfolded-transition: ();
|
||||
@for $i from 1 through length($default-properties) {
|
||||
$p: null;
|
||||
@if $i <= length($transition) {
|
||||
$p: nth($transition, $i);
|
||||
} @else {
|
||||
$p: nth($defaultProperties, $i);
|
||||
$p: nth($default-properties, $i);
|
||||
}
|
||||
$unfoldedTransition: append($unfoldedTransition, $p);
|
||||
$unfolded-transition: append($unfolded-transition, $p);
|
||||
}
|
||||
|
||||
@return $unfoldedTransition;
|
||||
@return $unfolded-transition;
|
||||
}
|
||||
|
||||
.btn {
|
||||
|
|
|
@ -15,7 +15,7 @@
|
|||
margin-top: 3px;
|
||||
padding: $gl-padding;
|
||||
z-index: 300;
|
||||
width: 300px;
|
||||
width: $award-emoji-width;
|
||||
font-size: 14px;
|
||||
background-color: $white-light;
|
||||
border: 1px solid $border-white-light;
|
||||
|
@ -55,6 +55,10 @@
|
|||
transform: none;
|
||||
}
|
||||
}
|
||||
|
||||
@include media-breakpoint-down(xs) {
|
||||
width: $award-emoji-width-xs;
|
||||
}
|
||||
}
|
||||
|
||||
.emoji-search {
|
||||
|
@ -229,10 +233,10 @@
|
|||
height: $default-icon-size;
|
||||
width: $default-icon-size;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
path {
|
||||
fill: $border-gray-normal;
|
||||
}
|
||||
path {
|
||||
fill: $border-gray-normal;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -243,6 +247,10 @@
|
|||
left: 10px;
|
||||
bottom: 6px;
|
||||
opacity: 0;
|
||||
|
||||
path {
|
||||
fill: $award-emoji-positive-add-lines;
|
||||
}
|
||||
}
|
||||
|
||||
.award-control-text {
|
||||
|
|
|
@ -166,7 +166,8 @@
|
|||
@include btn-outline($white-light, $green-600, $green-500, $green-500, $white-light, $green-600, $green-600, $green-700);
|
||||
}
|
||||
|
||||
&.btn-remove {
|
||||
&.btn-remove,
|
||||
&.btn-danger {
|
||||
@include btn-outline($white-light, $red-500, $red-500, $red-500, $white-light, $red-600, $red-600, $red-700);
|
||||
}
|
||||
|
||||
|
|
|
@ -48,6 +48,10 @@
|
|||
color: $brand-info;
|
||||
}
|
||||
|
||||
.text-break-word {
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
.hint { font-style: italic; color: $gl-gray-400; }
|
||||
.light { color: $gl-text-color; }
|
||||
|
||||
|
@ -442,3 +446,15 @@ img.emoji {
|
|||
.position-left-0 { left: 0; }
|
||||
.position-right-0 { right: 0; }
|
||||
.position-top-0 { top: 0; }
|
||||
|
||||
.drag-handle {
|
||||
width: 4px;
|
||||
|
||||
&:hover {
|
||||
background-color: $white-normal;
|
||||
}
|
||||
|
||||
&.is-dragging {
|
||||
background-color: $gray-600;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -565,15 +565,14 @@
|
|||
}
|
||||
|
||||
.navbar-empty {
|
||||
justify-content: center;
|
||||
height: $header-height;
|
||||
background: $white-light;
|
||||
border-bottom: 1px solid $white-normal;
|
||||
|
||||
.mx-auto {
|
||||
.tanuki-logo,
|
||||
img {
|
||||
height: 36px;
|
||||
}
|
||||
.tanuki-logo,
|
||||
.brand-header-logo {
|
||||
max-height: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -228,7 +228,7 @@
|
|||
|
||||
.cur {
|
||||
.avatar {
|
||||
@include disableAllAnimation;
|
||||
@include disable-all-animation;
|
||||
border: 1px solid $white-light;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -36,10 +36,6 @@
|
|||
width: fit-content;
|
||||
}
|
||||
|
||||
tbody {
|
||||
background-color: $white-light;
|
||||
}
|
||||
|
||||
tr {
|
||||
th {
|
||||
border-bottom: solid 2px $gl-gray-100;
|
||||
|
|
|
@ -111,10 +111,11 @@ body.modal-open {
|
|||
flex-grow: 1;
|
||||
height: 56px;
|
||||
padding: $gl-btn-padding $gl-btn-padding 0;
|
||||
text-align: right;
|
||||
|
||||
> svg {
|
||||
float: right;
|
||||
height: 100%;
|
||||
.illustration {
|
||||
height: inherit;
|
||||
width: initial;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -49,13 +49,6 @@
|
|||
word-wrap: normal;
|
||||
}
|
||||
|
||||
// Multi-line code blocks should scroll horizontally
|
||||
pre {
|
||||
code {
|
||||
white-space: pre;
|
||||
}
|
||||
}
|
||||
|
||||
kbd {
|
||||
display: inline-block;
|
||||
padding: 3px 5px;
|
||||
|
@ -166,6 +159,10 @@
|
|||
overflow-x: auto;
|
||||
border-radius: 2px;
|
||||
|
||||
// Multi-line code blocks should scroll horizontally
|
||||
code {
|
||||
white-space: pre;
|
||||
}
|
||||
|
||||
&.plain-readme {
|
||||
background: none;
|
||||
|
@ -303,11 +300,10 @@ body {
|
|||
}
|
||||
|
||||
.page-title-empty {
|
||||
margin-top: 0;
|
||||
margin: 12px 0;
|
||||
line-height: 1.3;
|
||||
font-size: 1.25em;
|
||||
font-weight: $gl-font-weight-bold;
|
||||
margin: 12px 0;
|
||||
}
|
||||
|
||||
h1,
|
||||
|
|
|
@ -251,7 +251,7 @@ $gl-padding-top: 10px;
|
|||
$gl-sidebar-padding: 22px;
|
||||
$gl-bar-padding: 3px;
|
||||
$input-horizontal-padding: 12px;
|
||||
$browserScrollbarSize: 10px;
|
||||
$browser-scrollbar-size: 10px;
|
||||
|
||||
/*
|
||||
* Misc
|
||||
|
@ -405,6 +405,8 @@ $status-icon-size: 22px;
|
|||
$award-emoji-menu-shadow: rgba(0, 0, 0, 0.175);
|
||||
$award-emoji-positive-add-bg: #fed159;
|
||||
$award-emoji-positive-add-lines: #bb9c13;
|
||||
$award-emoji-width: 376px;
|
||||
$award-emoji-width-xs: 300px;
|
||||
|
||||
/*
|
||||
* Search Box
|
||||
|
|
|
@ -125,7 +125,7 @@ $dark-il: #de935f;
|
|||
|
||||
.diff-line-num.new,
|
||||
.line_content.new {
|
||||
@include diff_background($dark-new-bg, $dark-new-idiff, $dark-border);
|
||||
@include diff-background($dark-new-bg, $dark-new-idiff, $dark-border);
|
||||
|
||||
&::before,
|
||||
a {
|
||||
|
@ -135,7 +135,7 @@ $dark-il: #de935f;
|
|||
|
||||
.diff-line-num.old,
|
||||
.line_content.old {
|
||||
@include diff_background($dark-old-bg, $dark-old-idiff, $dark-border);
|
||||
@include diff-background($dark-old-bg, $dark-old-idiff, $dark-border);
|
||||
|
||||
&::before,
|
||||
a {
|
||||
|
|
|
@ -125,7 +125,7 @@ $monokai-gi: #a6e22e;
|
|||
|
||||
.diff-line-num.new,
|
||||
.line_content.new {
|
||||
@include diff_background($monokai-new-bg, $monokai-new-idiff, $monokai-diff-border);
|
||||
@include diff-background($monokai-new-bg, $monokai-new-idiff, $monokai-diff-border);
|
||||
|
||||
&::before,
|
||||
a {
|
||||
|
@ -135,7 +135,7 @@ $monokai-gi: #a6e22e;
|
|||
|
||||
.diff-line-num.old,
|
||||
.line_content.old {
|
||||
@include diff_background($monokai-old-bg, $monokai-old-idiff, $monokai-diff-border);
|
||||
@include diff-background($monokai-old-bg, $monokai-old-idiff, $monokai-diff-border);
|
||||
|
||||
&::before,
|
||||
a {
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
|
||||
|
||||
|
||||
@mixin matchLine {
|
||||
@mixin match-line {
|
||||
color: $black-transparent;
|
||||
background-color: $white-normal;
|
||||
}
|
||||
|
@ -45,7 +45,7 @@
|
|||
&.match .line_content,
|
||||
.new-nonewline.line_content,
|
||||
.old-nonewline.line_content {
|
||||
@include matchLine;
|
||||
@include match-line;
|
||||
}
|
||||
|
||||
.diff-line-num {
|
||||
|
@ -121,7 +121,7 @@
|
|||
}
|
||||
|
||||
&.match {
|
||||
@include matchLine;
|
||||
@include match-line;
|
||||
}
|
||||
|
||||
&.hll:not(.empty-cell) {
|
||||
|
|
|
@ -129,7 +129,7 @@ $solarized-dark-il: #2aa198;
|
|||
|
||||
.diff-line-num.new,
|
||||
.line_content.new {
|
||||
@include diff_background($solarized-dark-new-bg, $solarized-dark-new-idiff, $solarized-dark-border);
|
||||
@include diff-background($solarized-dark-new-bg, $solarized-dark-new-idiff, $solarized-dark-border);
|
||||
|
||||
&::before,
|
||||
a {
|
||||
|
@ -139,7 +139,7 @@ $solarized-dark-il: #2aa198;
|
|||
|
||||
.diff-line-num.old,
|
||||
.line_content.old {
|
||||
@include diff_background($solarized-dark-old-bg, $solarized-dark-old-idiff, $solarized-dark-border);
|
||||
@include diff-background($solarized-dark-old-bg, $solarized-dark-old-idiff, $solarized-dark-border);
|
||||
|
||||
&::before,
|
||||
a {
|
||||
|
|
|
@ -90,7 +90,7 @@ $solarized-light-vg: #268bd2;
|
|||
$solarized-light-vi: #268bd2;
|
||||
$solarized-light-il: #2aa198;
|
||||
|
||||
@mixin matchLine {
|
||||
@mixin match-line {
|
||||
color: $black-transparent;
|
||||
background: $solarized-light-matchline-bg;
|
||||
}
|
||||
|
@ -125,7 +125,7 @@ $solarized-light-il: #2aa198;
|
|||
&.match .line_content,
|
||||
&.old-nonewline .line_content,
|
||||
&.new-nonewline .line_content {
|
||||
@include matchLine;
|
||||
@include match-line;
|
||||
}
|
||||
|
||||
td.diff-line-num.hll:not(.empty-cell),
|
||||
|
@ -136,7 +136,7 @@ $solarized-light-il: #2aa198;
|
|||
|
||||
.diff-line-num.new,
|
||||
.line_content.new {
|
||||
@include diff_background($solarized-light-new-bg,
|
||||
@include diff-background($solarized-light-new-bg,
|
||||
$solarized-light-new-idiff, $solarized-light-border);
|
||||
|
||||
&::before,
|
||||
|
@ -147,7 +147,7 @@ $solarized-light-il: #2aa198;
|
|||
|
||||
.diff-line-num.old,
|
||||
.line_content.old {
|
||||
@include diff_background($solarized-light-old-bg, $solarized-light-old-idiff, $solarized-light-border);
|
||||
@include diff-background($solarized-light-old-bg, $solarized-light-old-idiff, $solarized-light-border);
|
||||
|
||||
&::before,
|
||||
a {
|
||||
|
@ -168,7 +168,7 @@ $solarized-light-il: #2aa198;
|
|||
}
|
||||
|
||||
.line_content.match {
|
||||
@include matchLine;
|
||||
@include match-line;
|
||||
}
|
||||
|
||||
&:not(.diff-expanded) + .diff-expanded,
|
||||
|
|
|
@ -70,7 +70,7 @@ $white-gc-color: #999;
|
|||
$white-gc-bg: #eaf2f5;
|
||||
|
||||
|
||||
@mixin matchLine {
|
||||
@mixin match-line {
|
||||
color: $black-transparent;
|
||||
background-color: $gray-light;
|
||||
}
|
||||
|
@ -105,7 +105,7 @@ pre.code,
|
|||
&.match .line_content,
|
||||
.new-nonewline.line_content,
|
||||
.old-nonewline.line_content {
|
||||
@include matchLine;
|
||||
@include match-line;
|
||||
}
|
||||
|
||||
.diff-line-num {
|
||||
|
@ -185,7 +185,7 @@ pre.code,
|
|||
}
|
||||
|
||||
&.match {
|
||||
@include matchLine;
|
||||
@include match-line;
|
||||
}
|
||||
|
||||
&.hll:not(.empty-cell) {
|
||||
|
|
|
@ -682,25 +682,6 @@ $ide-commit-header-height: 48px;
|
|||
flex: 1;
|
||||
}
|
||||
|
||||
.drag-handle {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
width: 4px;
|
||||
|
||||
&:hover {
|
||||
background-color: $white-normal;
|
||||
}
|
||||
|
||||
&.drag-right {
|
||||
right: 0;
|
||||
}
|
||||
|
||||
&.drag-left {
|
||||
left: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.ide-commit-list-container {
|
||||
display: flex;
|
||||
flex: 1;
|
||||
|
|
|
@ -164,6 +164,13 @@
|
|||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
&:not(.is-collapsed) {
|
||||
.board-list-component {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.board-inner {
|
||||
|
|
|
@ -11,15 +11,24 @@
|
|||
}
|
||||
|
||||
.divergence-graph {
|
||||
$graph-side-width: 80px;
|
||||
$graph-separator-width: 1px;
|
||||
|
||||
padding: 0 6px;
|
||||
|
||||
.graph-side {
|
||||
position: relative;
|
||||
width: 80px;
|
||||
width: $graph-side-width;
|
||||
height: 22px;
|
||||
padding: 5px 0 13px;
|
||||
float: left;
|
||||
|
||||
&.full {
|
||||
width: $graph-side-width * 2 + $graph-separator-width;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.bar {
|
||||
position: absolute;
|
||||
height: 4px;
|
||||
|
@ -57,7 +66,7 @@
|
|||
|
||||
.graph-separator {
|
||||
position: relative;
|
||||
width: 1px;
|
||||
width: $graph-separator-width;
|
||||
height: 18px;
|
||||
margin: 5px 0 0;
|
||||
float: left;
|
||||
|
|
|
@ -34,7 +34,6 @@
|
|||
|
||||
.detail-page-header-actions {
|
||||
align-self: center;
|
||||
flex-shrink: 0;
|
||||
flex: 0 0 auto;
|
||||
|
||||
@include media-breakpoint-down(xs) {
|
||||
|
|
|
@ -602,7 +602,7 @@
|
|||
}
|
||||
}
|
||||
|
||||
@mixin diff_background($background, $idiff, $border) {
|
||||
@mixin diff-background($background, $idiff, $border) {
|
||||
background: $background;
|
||||
|
||||
&.line_content span.idiff {
|
||||
|
@ -1038,12 +1038,30 @@
|
|||
}
|
||||
|
||||
.diff-tree-list {
|
||||
width: 320px;
|
||||
position: -webkit-sticky;
|
||||
position: sticky;
|
||||
$top-pos: $header-height + $mr-tabs-height + $mr-version-controls-height + 10px;
|
||||
top: $header-height + $mr-tabs-height + $mr-version-controls-height + 10px;
|
||||
max-height: calc(100vh - #{$top-pos});
|
||||
padding-right: $gl-padding;
|
||||
z-index: 202;
|
||||
|
||||
.with-performance-bar & {
|
||||
$performance-bar-top-pos: $performance-bar-height + $top-pos;
|
||||
top: $performance-bar-top-pos;
|
||||
max-height: calc(100vh - #{$performance-bar-top-pos});
|
||||
}
|
||||
|
||||
.drag-handle {
|
||||
bottom: 16px;
|
||||
transform: translateX(-6px);
|
||||
}
|
||||
}
|
||||
|
||||
.diff-files-holder {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
z-index: 201;
|
||||
}
|
||||
|
||||
.compare-versions-container {
|
||||
|
@ -1051,23 +1069,12 @@
|
|||
}
|
||||
|
||||
.tree-list-holder {
|
||||
position: -webkit-sticky;
|
||||
position: sticky;
|
||||
$top-pos: $header-height + $mr-tabs-height + $mr-version-controls-height + 10px;
|
||||
top: $header-height + $mr-tabs-height + $mr-version-controls-height + 10px;
|
||||
max-height: calc(100vh - #{$top-pos});
|
||||
padding-right: $gl-padding;
|
||||
height: 100%;
|
||||
|
||||
.file-row {
|
||||
margin-left: 0;
|
||||
margin-right: 0;
|
||||
}
|
||||
|
||||
.with-performance-bar & {
|
||||
$performance-bar-top-pos: $performance-bar-height + $top-pos;
|
||||
top: $performance-bar-top-pos;
|
||||
max-height: calc(100vh - #{$performance-bar-top-pos});
|
||||
}
|
||||
}
|
||||
|
||||
.tree-list-scroll {
|
||||
|
|
|
@ -182,9 +182,8 @@
|
|||
|
||||
.template-selector-dropdowns-wrap {
|
||||
display: inline-block;
|
||||
margin-left: 8px;
|
||||
vertical-align: top;
|
||||
margin: 5px 0 0 8px;
|
||||
vertical-align: top;
|
||||
|
||||
@media(max-width: map-get($grid-breakpoints, md)-1) {
|
||||
display: block;
|
||||
|
|
53
app/assets/stylesheets/pages/import.scss
vendored
53
app/assets/stylesheets/pages/import.scss
vendored
|
@ -1,20 +1,51 @@
|
|||
.import-jobs-from-col,
|
||||
.import-jobs-to-col {
|
||||
width: 40%;
|
||||
width: 39%;
|
||||
}
|
||||
|
||||
.import-jobs-status-col {
|
||||
width: 20%;
|
||||
width: 15%;
|
||||
}
|
||||
|
||||
.btn-import {
|
||||
.loading-icon {
|
||||
display: none;
|
||||
}
|
||||
.import-jobs-cta-col {
|
||||
width: 1%;
|
||||
}
|
||||
|
||||
&.is-loading {
|
||||
.loading-icon {
|
||||
display: inline-block;
|
||||
}
|
||||
.import-project-name-input {
|
||||
border-radius: 0 $border-radius-default $border-radius-default 0;
|
||||
position: relative;
|
||||
left: -1px;
|
||||
max-width: 300px;
|
||||
}
|
||||
|
||||
.import-namespace-select {
|
||||
width: auto !important;
|
||||
|
||||
> .select2-choice {
|
||||
border-radius: $border-radius-default 0 0 $border-radius-default;
|
||||
position: relative;
|
||||
left: 1px;
|
||||
}
|
||||
}
|
||||
|
||||
.import-slash-divider {
|
||||
background-color: $gray-lightest;
|
||||
border: 1px solid $border-color;
|
||||
}
|
||||
|
||||
.import-row {
|
||||
height: 55px;
|
||||
}
|
||||
|
||||
.import-table {
|
||||
.import-jobs-from-col,
|
||||
.import-jobs-to-col,
|
||||
.import-jobs-status-col,
|
||||
.import-jobs-cta-col {
|
||||
border-bottom-width: 1px;
|
||||
padding-left: $gl-padding;
|
||||
}
|
||||
}
|
||||
|
||||
.import-projects-loading-icon {
|
||||
margin-top: $gl-padding-32;
|
||||
}
|
||||
|
|
|
@ -735,9 +735,11 @@
|
|||
|
||||
.mr-version-controls {
|
||||
position: relative;
|
||||
z-index: 103;
|
||||
z-index: 203;
|
||||
background: $gray-light;
|
||||
color: $gl-text-color;
|
||||
margin-top: -1px;
|
||||
border-top: 1px solid $border-color;
|
||||
|
||||
.mr-version-menus-container {
|
||||
display: flex;
|
||||
|
@ -789,7 +791,6 @@
|
|||
position: sticky;
|
||||
top: $header-height + $mr-tabs-height;
|
||||
width: 100%;
|
||||
border-top: 1px solid $border-color;
|
||||
|
||||
&.is-fileTreeOpen {
|
||||
margin-left: -16px;
|
||||
|
@ -808,12 +809,9 @@
|
|||
|
||||
.merge-request-tabs-holder {
|
||||
top: $header-height;
|
||||
z-index: 200;
|
||||
z-index: 300;
|
||||
background-color: $white-light;
|
||||
|
||||
@include media-breakpoint-down(md) {
|
||||
border-bottom: 1px solid $border-color;
|
||||
}
|
||||
border-bottom: 1px solid $border-color;
|
||||
|
||||
@include media-breakpoint-up(sm) {
|
||||
position: sticky;
|
||||
|
@ -1019,3 +1017,8 @@
|
|||
z-index: 99999;
|
||||
background: $black-transparent;
|
||||
}
|
||||
|
||||
.source-branch-removal-status {
|
||||
padding-left: 50px;
|
||||
padding-bottom: $gl-padding;
|
||||
}
|
||||
|
|
|
@ -494,11 +494,6 @@ $note-form-margin-left: 72px;
|
|||
.discussion-notes {
|
||||
margin-left: 0;
|
||||
border-left: 0;
|
||||
|
||||
.notes {
|
||||
position: relative;
|
||||
@include vertical-line(52px);
|
||||
}
|
||||
}
|
||||
|
||||
.note-wrapper {
|
||||
|
@ -550,6 +545,11 @@ $note-form-margin-left: 72px;
|
|||
.note-header-info {
|
||||
padding-bottom: 0;
|
||||
}
|
||||
|
||||
.timeline-content {
|
||||
overflow-x: auto;
|
||||
overflow-y: hidden;
|
||||
}
|
||||
}
|
||||
|
||||
.unresolved {
|
||||
|
@ -597,7 +597,6 @@ $note-form-margin-left: 72px;
|
|||
|
||||
.note-headline-meta {
|
||||
display: inline-block;
|
||||
white-space: nowrap;
|
||||
|
||||
.system-note-message {
|
||||
white-space: normal;
|
||||
|
@ -607,6 +606,10 @@ $note-form-margin-left: 72px;
|
|||
color: $gl-text-color-disabled;
|
||||
}
|
||||
|
||||
.note-timestamp {
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
|
|
@ -704,8 +704,8 @@
|
|||
.scrolling-tabs-container {
|
||||
.scrolling-tabs {
|
||||
margin-top: $gl-padding-8;
|
||||
margin-bottom: $gl-padding-8 - $browserScrollbarSize;
|
||||
padding-bottom: $browserScrollbarSize;
|
||||
margin-bottom: $gl-padding-8 - $browser-scrollbar-size;
|
||||
padding-bottom: $browser-scrollbar-size;
|
||||
flex-wrap: wrap;
|
||||
border-bottom: 0;
|
||||
}
|
||||
|
@ -713,7 +713,7 @@
|
|||
.fade-left,
|
||||
.fade-right {
|
||||
top: 0;
|
||||
height: calc(100% - #{$browserScrollbarSize});
|
||||
height: calc(100% - #{$browser-scrollbar-size});
|
||||
|
||||
.fa {
|
||||
top: 50%;
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class Admin::UsersController < Admin::ApplicationController
|
||||
include RoutableActions
|
||||
|
||||
before_action :user, except: [:index, :new, :create]
|
||||
before_action :check_impersonation_availability, only: :impersonate
|
||||
|
||||
|
@ -177,11 +179,13 @@ class Admin::UsersController < Admin::ApplicationController
|
|||
user == current_user
|
||||
end
|
||||
|
||||
# rubocop: disable CodeReuse/ActiveRecord
|
||||
def user
|
||||
@user ||= User.find_by!(username: params[:id])
|
||||
@user ||= find_routable!(User, params[:id])
|
||||
end
|
||||
|
||||
def build_canonical_path(user)
|
||||
url_for(safe_params.merge(id: user.to_param))
|
||||
end
|
||||
# rubocop: enable CodeReuse/ActiveRecord
|
||||
|
||||
def redirect_back_or_admin_user(options = {})
|
||||
redirect_back_or_default(default: default_route, options: options)
|
||||
|
|
|
@ -137,6 +137,8 @@ class ApplicationController < ActionController::Base
|
|||
if response.status == 422 && response.body.present? && response.content_type == 'application/json'.freeze
|
||||
payload[:response] = response.body
|
||||
end
|
||||
|
||||
payload[:queue_duration] = request.env[::Gitlab::Middleware::RailsQueueDuration::GITLAB_RAILS_QUEUE_DURATION_KEY]
|
||||
end
|
||||
|
||||
##
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
module SendFileUpload
|
||||
def send_upload(file_upload, send_params: {}, redirect_params: {}, attachment: nil, proxy: false, disposition: 'attachment')
|
||||
if attachment
|
||||
response_disposition = ::Gitlab::ContentDisposition.format(disposition: 'attachment', filename: attachment)
|
||||
response_disposition = ::Gitlab::ContentDisposition.format(disposition: disposition, filename: attachment)
|
||||
|
||||
# Response-Content-Type will not override an existing Content-Type in
|
||||
# Google Cloud Storage, so the metadata needs to be cleared on GCS for
|
||||
|
|
|
@ -25,8 +25,6 @@ class Dashboard::MilestonesController < Dashboard::ApplicationController
|
|||
private
|
||||
|
||||
def group_milestones
|
||||
groups = GroupsFinder.new(current_user, all_available: false).execute
|
||||
|
||||
DashboardGroupMilestone.build_collection(groups, params)
|
||||
end
|
||||
|
||||
|
@ -45,6 +43,6 @@ class Dashboard::MilestonesController < Dashboard::ApplicationController
|
|||
end
|
||||
|
||||
def groups
|
||||
@groups ||= GroupsFinder.new(current_user, state_all: true).execute
|
||||
@groups ||= GroupsFinder.new(current_user, all_available: false).execute
|
||||
end
|
||||
end
|
||||
|
|
|
@ -13,9 +13,10 @@ class HelpController < ApplicationController
|
|||
# Remove YAML frontmatter so that it doesn't look weird
|
||||
@help_index = File.read(Rails.root.join('doc', 'README.md')).sub(YAML_FRONT_MATTER_REGEXP, '')
|
||||
|
||||
# Prefix Markdown links with `help/` unless they are external links
|
||||
# See http://rubular.com/r/X3baHTbPO2
|
||||
@help_index.gsub!(%r{(?<delim>\]\()(?!.+://)(?!/)(?<link>[^\)\(]+\))}) do
|
||||
# Prefix Markdown links with `help/` unless they are external links.
|
||||
# '//' not necessarily part of URL, e.g., mailto:mail@example.com
|
||||
# See https://rubular.com/r/DFHZl5w8d3bpzV
|
||||
@help_index.gsub!(%r{(?<delim>\]\()(?!\w+:)(?!/)(?<link>[^\)\(]+\))}) do
|
||||
"#{$~[:delim]}#{Gitlab.config.gitlab.relative_url_root}/help/#{$~[:link]}"
|
||||
end
|
||||
end
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue