Коммит 3551d6a3 создал по автору Filipa Lacerda's avatar Filipa Lacerda
Просмотр файлов

Merge branch 'ide-code-cleanup' into 'master'

Tidy up IDE code

See merge request gitlab-org/gitlab-ee!4951
владельцы a258b730 79137a95
......@@ -68,7 +68,6 @@
.ide-new-btn {
display: none;
margin-top: -4px;
margin-bottom: -4px;
margin-right: -8px;
}
......@@ -84,7 +83,6 @@
fill: $gl-text-color-secondary;
}
}
}
a {
......@@ -290,7 +288,7 @@
.margin-view-overlays .insert-sign,
.margin-view-overlays .delete-sign {
opacity: .4;
opacity: 0.4;
}
}
}
......@@ -548,7 +546,6 @@
height: 10px;
margin-left: 3px;
}
}
.multi-file-commit-list-path {
......@@ -626,7 +623,7 @@
top: 0;
width: 100px;
height: 1px;
background-color: rgba($red-500, .5);
background-color: rgba($red-500, 0.5);
}
}
}
......@@ -720,12 +717,13 @@
}
.ide-view {
height: calc(100vh - #{$header-height + $performance-bar-height + $flash-height});
height: calc(
100vh - #{$header-height + $performance-bar-height + $flash-height}
);
}
}
}
.dragHandle {
position: absolute;
top: 0;
......
......@@ -52,7 +52,7 @@
<button
type="button"
class="btn btn-blank multi-file-discard-btn"
@click="discardFileChanges(file)"
@click="discardFileChanges(file.path)"
>
Discard
</button>
......
......@@ -5,7 +5,6 @@
import repoTabs from './repo_tabs.vue';
import repoFileButtons from './repo_file_buttons.vue';
import ideStatusBar from './ide_status_bar.vue';
import repoPreview from './repo_preview.vue';
import repoEditor from './repo_editor.vue';
export default {
......@@ -16,7 +15,6 @@
repoFileButtons,
ideStatusBar,
repoEditor,
repoPreview,
},
props: {
emptyStateSvgPath: {
......@@ -33,18 +31,12 @@
},
},
computed: {
...mapState([
'currentBlobView',
'selectedFile',
'changedFiles',
]),
...mapGetters([
'activeFile',
]),
...mapState(['changedFiles', 'openFiles', 'viewer']),
...mapGetters(['activeFile', 'hasChanges']),
},
mounted() {
const returnValue = 'Are you sure you want to lose unsaved changes?';
window.onbeforeunload = (e) => {
window.onbeforeunload = e => {
if (!this.changedFiles.length) return undefined;
Object.assign(e, {
......@@ -67,20 +59,29 @@
<template
v-if="activeFile"
>
<repo-tabs/>
<component
<repo-tabs
:files="openFiles"
:viewer="viewer"
:has-changes="hasChanges"
/>
<repo-editor
class="multi-file-edit-pane-content"
:is="currentBlobView"
:file="activeFile"
/>
<repo-file-buttons
:file="activeFile"
/>
<repo-file-buttons />
<ide-status-bar
:file="selectedFile"
:file="activeFile"
/>
</template>
<template
v-else
>
<div class="ide-empty-state">
<div
v-once
class="ide-empty-state"
>
<div class="row js-empty-state">
<div class="col-xs-12">
<div class="svg-content svg-250">
......
<script>
import { mapState, mapActions } from 'vuex';
import icon from '~/vue_shared/components/icon.vue';
import panelResizer from '~/vue_shared/components/panel_resizer.vue';
import repoCommitSection from './repo_commit_section.vue';
import { mapActions, mapGetters, mapState } from 'vuex';
import icon from '~/vue_shared/components/icon.vue';
import panelResizer from '~/vue_shared/components/panel_resizer.vue';
import repoCommitSection from './repo_commit_section.vue';
import ResizablePanel from './resizable_panel.vue';
export default {
components: {
repoCommitSection,
icon,
panelResizer,
export default {
components: {
repoCommitSection,
icon,
panelResizer,
ResizablePanel,
},
props: {
noChangesStateSvgPath: {
type: String,
required: true,
},
props: {
noChangesStateSvgPath: {
type: String,
required: true,
},
committedStateSvgPath: {
type: String,
required: true,
},
committedStateSvgPath: {
type: String,
required: true,
},
data() {
return {
width: 340,
};
},
computed: {
...mapState([
'rightPanelCollapsed',
'changedFiles',
]),
currentIcon() {
return this.rightPanelCollapsed ? 'angle-double-left' : 'angle-double-right';
},
maxSize() {
return window.innerWidth / 2;
},
panelStyle() {
if (!this.rightPanelCollapsed) {
return { width: `${this.width}px` };
}
return {};
},
},
methods: {
...mapActions([
'setPanelCollapsedStatus',
'setResizingStatus',
]),
toggleCollapsed() {
this.setPanelCollapsedStatus({
side: 'right',
collapsed: !this.rightPanelCollapsed,
});
},
toggleFullbarCollapsed() {
if (this.rightPanelCollapsed) {
this.toggleCollapsed();
}
},
resizingStarted() {
this.setResizingStatus(true);
},
resizingEnded() {
this.setResizingStatus(false);
},
},
};
},
computed: {
...mapState(['changedFiles', 'rightPanelCollapsed']),
...mapGetters(['currentIcon']),
},
methods: {
...mapActions(['setPanelCollapsedStatus']),
},
};
</script>
<template>
<div
class="multi-file-commit-panel"
:class="{
'is-collapsed': rightPanelCollapsed,
}"
:style="panelStyle"
@click="toggleFullbarCollapsed"
<resizable-panel
:collapsible="true"
:initial-width="340"
side="right"
>
<div
class="multi-file-commit-panel-section"
......@@ -104,7 +64,10 @@
<button
type="button"
class="btn btn-transparent multi-file-commit-panel-collapse-btn"
@click.stop="toggleCollapsed"
@click.stop="setPanelCollapsedStatus({
side: 'right',
collapsed: !rightPanelCollapsed,
})"
>
<icon
:name="currentIcon"
......@@ -117,15 +80,5 @@
:committed-state-svg-path="committedStateSvgPath"
/>
</div>
<panel-resizer
:size.sync="width"
:enabled="!rightPanelCollapsed"
:start-size="340"
:min-size="200"
:max-size="maxSize"
@resize-start="resizingStarted"
@resize-end="resizingEnded"
side="left"
/>
</div>
</resizable-panel>
</template>
<script>
import icon from '~/vue_shared/components/icon.vue';
import repoTree from './ide_repo_tree.vue';
import newDropdown from './new_dropdown/index.vue';
import icon from '~/vue_shared/components/icon.vue';
import repoTree from './ide_repo_tree.vue';
import newDropdown from './new_dropdown/index.vue';
export default {
components: {
repoTree,
icon,
newDropdown,
},
props: {
projectId: {
type: String,
required: true,
export default {
components: {
repoTree,
icon,
newDropdown,
},
branch: {
type: Object,
required: true,
props: {
projectId: {
type: String,
required: true,
},
branch: {
type: Object,
required: true,
},
},
},
};
};
</script>
<template>
......@@ -40,8 +40,8 @@ export default {
/>
</div>
</div>
<div>
<repo-tree :tree-id="branch.treeId" />
</div>
<repo-tree
:tree="branch.tree"
/>
</div>
</template>
<script>
import { mapState } from 'vuex';
import skeletonLoadingContainer from '~/vue_shared/components/skeleton_loading_container.vue';
import repoFile from './repo_file.vue';
import SkeletonLoadingContainer from '~/vue_shared/components/skeleton_loading_container.vue';
import RepoFile from './repo_file.vue';
export default {
components: {
repoFile,
skeletonLoadingContainer,
RepoFile,
SkeletonLoadingContainer,
},
props: {
treeId: {
type: String,
tree: {
type: Object,
required: true,
},
},
computed: {
...mapState([
'trees',
]),
...mapState({
projectName(state) {
return state.project.name;
},
}),
selctedTree() {
return this.trees[this.treeId].tree;
},
showLoading() {
return !this.trees[this.treeId] || this.trees[this.treeId].loading;
},
},
};
</script>
<template>
<div
class="ide-file-list"
v-if="treeId"
>
<template v-if="showLoading">
<template v-if="tree.loading">
<div
class="multi-file-loading-container"
v-for="n in 3"
......@@ -47,10 +29,13 @@ export default {
<skeleton-loading-container />
</div>
</template>
<repo-file
v-for="file in selctedTree"
:key="file.key"
:file="file"
/>
<template v-else>
<repo-file
v-for="file in tree.tree"
:key="file.key"
:file="file"
:level="0"
/>
</template>
</div>
</template>
<script>
import { mapState, mapActions } from 'vuex';
import { mapState, mapGetters } from 'vuex';
import icon from '~/vue_shared/components/icon.vue';
import panelResizer from '~/vue_shared/components/panel_resizer.vue';
import skeletonLoadingContainer from '~/vue_shared/components/skeleton_loading_container.vue';
import projectTree from './ide_project_tree.vue';
import ResizablePanel from './resizable_panel.vue';
export default {
components: {
......@@ -11,65 +12,27 @@
icon,
panelResizer,
skeletonLoadingContainer,
},
data() {
return {
width: 290,
};
ResizablePanel,
},
computed: {
...mapState([
'loading',
'projects',
'leftPanelCollapsed',
]),
currentIcon() {
return this.leftPanelCollapsed ? 'angle-double-right' : 'angle-double-left';
},
maxSize() {
return window.innerWidth / 2;
},
panelStyle() {
if (!this.leftPanelCollapsed) {
return { width: `${this.width}px` };
}
return {};
},
showLoading() {
return this.loading;
},
},
methods: {
...mapActions([
'setPanelCollapsedStatus',
'setResizingStatus',
...mapGetters([
'projectsWithTrees',
]),
toggleCollapsed() {
this.setPanelCollapsedStatus({
side: 'left',
collapsed: !this.leftPanelCollapsed,
});
},
resizingStarted() {
this.setResizingStatus(true);
},
resizingEnded() {
this.setResizingStatus(false);
},
},
};
</script>
<template>
<div
class="multi-file-commit-panel"
:class="{
'is-collapsed': leftPanelCollapsed,
}"
:style="panelStyle"
<resizable-panel
:collapsible="false"
:initial-width="290"
side="left"
>
<div class="multi-file-commit-panel-inner">
<template v-if="showLoading">
<template v-if="loading">
<div
class="multi-file-loading-container"
v-for="n in 3"
......@@ -79,36 +42,10 @@
</div>
</template>
<project-tree
v-for="project in projects"
v-for="project in projectsWithTrees"
:key="project.id"
:project="project"
/>
</div>
<button
type="button"
class="btn btn-transparent left-collapse-btn"
@click="toggleCollapsed"
>
<icon
:name="currentIcon"
:size="18"
/>
<span
v-if="!leftPanelCollapsed"
class="collapse-text"
>
Collapse sidebar
</span>
</button>
<panel-resizer
:size.sync="width"
:enabled="!leftPanelCollapsed"
:start-size="290"
:min-size="200"
:max-size="maxSize"
@resize-start="resizingStarted"
@resize-end="resizingEnded"
side="right"
/>
</div>
</resizable-panel>
</template>
<script>
import { mapState } from 'vuex';
import icon from '~/vue_shared/components/icon.vue';
import tooltip from '~/vue_shared/directives/tooltip';
import timeAgoMixin from '~/vue_shared/mixins/timeago';
......@@ -20,11 +19,6 @@
required: true,
},
},
computed: {
...mapState([
'selectedFile',
]),
},
};
</script>
......@@ -35,32 +29,32 @@
name="branch"
:size="12"
/>
{{ selectedFile.branchId }}
{{ file.branchId }}
</div>
<div>
<div v-if="selectedFile.lastCommit && selectedFile.lastCommit.id">
<div v-if="file.lastCommit && file.lastCommit.id">
Last commit:
<a
v-tooltip
:title="selectedFile.lastCommit.message"
:href="selectedFile.lastCommit.url"
:title="file.lastCommit.message"
:href="file.lastCommit.url"
>
{{ timeFormated(selectedFile.lastCommit.updatedAt) }} by
{{ selectedFile.lastCommit.author }}
{{ timeFormated(file.lastCommit.updatedAt) }} by
{{ file.lastCommit.author }}
</a>
</div>
</div>
<div class="text-right">
{{ selectedFile.name }}
{{ file.name }}
</div>
<div class="text-right">
{{ selectedFile.eol }}
{{ file.eol }}
</div>
<div class="text-right">
{{ file.editorRow }}:{{ file.editorColumn }}
</div>
<div class="text-right">
{{ selectedFile.fileLanguage }}
{{ file.fileLanguage }}
</div>
</div>
</template>
<script>
import $ from 'jquery';
import { mapState, mapActions } from 'vuex';
import flash, { hideFlash } from '~/flash';
import loadingIcon from '~/vue_shared/components/loading_icon.vue';
export default {
components: {
loadingIcon,
},
data() {
return {
branchName: '',
loading: false,
};
},
computed: {
...mapState([
'currentBranch',
]),
btnDisabled() {
return this.loading || this.branchName === '';
},
},
created() {
// Dropdown is outside of Vue instance & is controlled by Bootstrap
this.$dropdown = $('.git-revision-dropdown');
// text element is outside Vue app
this.dropdownText = document.querySelector('.project-refs-form .dropdown-toggle-text');
},
methods: {
...mapActions([
'createNewBranch',
]),
toggleDropdown() {
this.$dropdown.dropdown('toggle');
},
submitNewBranch() {
// need to query as the element is appended outside of Vue
const flashEl = this.$refs.flashContainer.querySelector('.flash-alert');
this.loading = true;
if (flashEl) {
hideFlash(flashEl, false);
}
this.createNewBranch(this.branchName)
.then(() => {
this.loading = false;
this.branchName = '';
if (this.dropdownText) {
this.dropdownText.textContent = this.currentBranchId;
}
this.toggleDropdown();
})
.catch(res => res.json().then((data) => {
this.loading = false;
flash(data.message, 'alert', this.$el);
}));
},
},
};
</script>
<template>
<div>
<div
class="flash-container"
ref="flashContainer"
>
</div>
<p>
Create from:
<code>{{ currentBranch }}</code>
</p>
<input
class="form-control js-new-branch-name"
type="text"
placeholder="Name new branch"
v-model="branchName"
@keyup.enter.stop.prevent="submitNewBranch"
/>
<div class="prepend-top-default clearfix">
<button
type="button"
class="btn btn-primary pull-left"
:disabled="btnDisabled"
@click.stop.prevent="submitNewBranch"
>
<loading-icon
v-if="loading"
:inline="true"
/>
<span>Create</span>
</button>
<button
type="button"
class="btn btn-default pull-right"
@click.stop.prevent="toggleDropdown"
>
Cancel
</button>
</div>
</div>
</template>
<script>
import { mapActions } from 'vuex';
import icon from '~/vue_shared/components/icon.vue';
import newModal from './modal.vue';
import upload from './upload.vue';
......@@ -18,10 +19,6 @@
type: String,
required: true,
},
parent: {
type: Object,
default: null,
},
},
data() {
return {
......@@ -31,6 +28,9 @@
};
},
methods: {
...mapActions([
'createTempEntry',
]),
createNewItem(type) {
this.modalType = type;
this.openModal = true;
......@@ -85,7 +85,7 @@
<upload
:branch-id="branch"
:path="path"
:parent="parent"
@create="createTempEntry"
/>
</li>
<li>
......@@ -104,8 +104,8 @@
:type="modalType"
:branch-id="branch"
:path="path"
:parent="parent"
@hide="hideModal"
@create="createTempEntry"
/>
</div>
</template>
<script>
import { mapActions, mapState } from 'vuex';
import { __ } from '~/locale';
import modal from '~/vue_shared/components/modal.vue';
......@@ -12,10 +11,6 @@
type: String,
required: true,
},
parent: {
type: Object,
default: null,
},
type: {
type: String,
required: true,
......@@ -31,9 +26,6 @@
};
},
computed: {
...mapState([
'currentProjectId',
]),
modalTitle() {
if (this.type === 'tree') {
return __('Create new directory');
......@@ -60,15 +52,10 @@
this.$refs.fieldName.focus();
},
methods: {
...mapActions([
'createTempEntry',
]),
createEntryInStore() {
this.createTempEntry({
projectId: this.currentProjectId,
this.$emit('create', {
branchId: this.branchId,
parent: this.parent,
name: this.entryName.replace(new RegExp(`^${this.path}/`), ''),
name: this.entryName,
type: this.type,
});
......
<script>
import { mapActions, mapState } from 'vuex';
export default {
props: {
branchId: {
type: String,
required: true,
},
parent: {
type: Object,
default: null,
path: {
type: String,
required: false,
default: '',
},
},
computed: {
...mapState([
'trees',
'currentProjectId',
]),
},
mounted() {
this.$refs.fileUpload.addEventListener('change', this.openFile);
},
......@@ -25,9 +18,6 @@
this.$refs.fileUpload.removeEventListener('change', this.openFile);
},
methods: {
...mapActions([
'createTempEntry',
]),
createFile(target, file, isText) {
const { name } = file;
let { result } = target;
......@@ -36,11 +26,9 @@
result = result.split('base64,')[1];
}
this.createTempEntry({
name,
projectId: this.currentProjectId,
this.$emit('create', {
name: `${(this.path ? `${this.path}/` : '')}${name}`,
branchId: this.branchId,
parent: this.parent,
type: 'blob',
content: result,
base64: !isText,
......
......@@ -53,7 +53,6 @@ export default {
},
methods: {
...mapActions([
'getTreeData',
'setPanelCollapsedStatus',
]),
...mapActions('commit', [
......
<script>
import { mapGetters, mapActions, mapState } from 'vuex';
import modal from '~/vue_shared/components/modal.vue';
export default {
components: {
modal,
},
computed: {
...mapState([
'editMode',
]),
...mapGetters([
'canEditFile',
]),
buttonLabel() {
return this.editMode ? this.__('Cancel edit') : this.__('Edit');
},
},
methods: {
...mapActions([
'toggleEditMode',
]),
},
};
</script>
<template>
<div class="editable-mode">
<button
v-if="canEditFile"
class="btn btn-default"
type="button"
@click.prevent="toggleEditMode()">
<i
v-if="!editMode"
class="fa fa-pencil"
aria-hidden="true">
</i>
<span>
{{ buttonLabel }}
</span>
</button>
</div>
</template>
<script>
/* global monaco */
import { mapState, mapGetters, mapActions } from 'vuex';
import { mapState, mapActions } from 'vuex';
import flash from '~/flash';
import monacoLoader from '../monaco_loader';
import Editor from '../lib/editor';
export default {
props: {
file: {
type: Object,
required: true,
},
},
computed: {
...mapGetters([
'activeFile',
'activeFileExtension',
]),
...mapState([
'leftPanelCollapsed',
'rightPanelCollapsed',
'panelResizing',
'viewer',
'delayViewerUpdated',
]),
shouldHideEditor() {
return this.activeFile && this.activeFile.binary && !this.activeFile.raw;
return this.file && this.file.binary && !this.file.raw;
},
},
watch: {
activeFile(oldVal, newVal) {
if (newVal && !newVal.active) {
file(oldVal, newVal) {
if (newVal.path !== this.file.path) {
this.initMonaco();
}
},
......@@ -34,11 +35,6 @@ export default {
rightPanelCollapsed() {
this.editor.updateDimensions();
},
panelResizing(isResizing) {
if (isResizing === false) {
this.editor.updateDimensions();
}
},
viewer() {
this.createEditorInstance();
},
......@@ -72,7 +68,7 @@ export default {
this.editor.clearEditor();
this.getRawFileData(this.activeFile)
this.getRawFileData(this.file)
.then(() => {
const viewerPromise = this.delayViewerUpdated ? this.updateViewer('editor') : Promise.resolve();
......@@ -101,9 +97,9 @@ export default {
});
},
setupEditor() {
if (!this.activeFile || !this.editor.instance) return;
if (!this.file || !this.editor.instance) return;
this.model = this.editor.createModel(this.activeFile);
this.model = this.editor.createModel(this.file);
this.editor.attachModel(this.model);
......@@ -112,7 +108,7 @@ export default {
if (file.active) {
this.changeFileContent({
file,
path: file.path,
content: model.getModel().getValue(),
});
}
......@@ -127,8 +123,8 @@ export default {
});
this.editor.setPosition({
lineNumber: this.activeFile.editorRow,
column: this.activeFile.editorColumn,
lineNumber: this.file.editorRow,
column: this.file.editorColumn,
});
// Handle File Language
......@@ -152,7 +148,7 @@ export default {
>
<div
v-if="shouldHideEditor"
v-html="activeFile.html"
v-html="file.html"
>
</div>
<div
......
<script>
import { mapActions, mapState } from 'vuex';
import { mapActions } from 'vuex';
import skeletonLoadingContainer from '~/vue_shared/components/skeleton_loading_container.vue';
import fileIcon from '~/vue_shared/components/file_icon.vue';
import router from '../ide_router';
import newDropdown from './new_dropdown/index.vue';
import fileStatusIcon from './repo_file_status_icon.vue';
import changedFileIcon from './changed_file_icon.vue';
import timeAgoMixin from '~/vue_shared/mixins/timeago';
import skeletonLoadingContainer from '~/vue_shared/components/skeleton_loading_container.vue';
import fileIcon from '~/vue_shared/components/file_icon.vue';
import newDropdown from './new_dropdown/index.vue';
import fileStatusIcon from 'ee/ide/components/repo_file_status_icon.vue'; // eslint-disable-line import/first
import changedFileIcon from 'ee/ide/components/changed_file_icon.vue'; // eslint-disable-line import/first
export default {
name: 'RepoFile',
components: {
skeletonLoadingContainer,
newDropdown,
fileStatusIcon,
fileIcon,
changedFileIcon,
export default {
name: 'RepoFile',
components: {
skeletonLoadingContainer,
newDropdown,
fileStatusIcon,
fileIcon,
changedFileIcon,
},
props: {
file: {
type: Object,
required: true,
},
mixins: [
timeAgoMixin,
],
props: {
file: {
type: Object,
required: true,
},
showExtraColumns: {
type: Boolean,
default: false,
},
level: {
type: Number,
required: true,
},
computed: {
...mapState([
'leftPanelCollapsed',
]),
isSubmodule() {
return this.file.type === 'submodule';
},
isTree() {
return this.file.type === 'tree';
},
levelIndentation() {
if (this.file.level > 0) {
return {
marginLeft: `${this.file.level * 16}px`,
};
}
return {};
},
shortId() {
return this.file.id.substr(0, 8);
},
fileClass() {
if (this.file.type === 'blob') {
if (this.file.active) {
return 'file-open file-active';
}
return this.file.opened ? 'file-open' : '';
} else if (this.file.type === 'tree') {
return 'folder';
}
return '';
},
},
computed: {
isTree() {
return this.file.type === 'tree';
},
updated() {
if (this.file.type === 'blob' && this.file.active) {
this.$el.scrollIntoView();
}
isBlob() {
return this.file.type === 'blob';
},
levelIndentation() {
return {
marginLeft: `${this.level * 16}px`,
};
},
methods: {
...mapActions([
'updateDelayViewerUpdated',
]),
clickFile(row) {
// Manual Action if a tree is selected/opened
if (this.file.type === 'tree' && this.$router.currentRoute.path === `/project${row.url}`) {
this.$store.dispatch('toggleTreeOpen', {
endpoint: this.file.url,
tree: this.file,
});
}
fileClass() {
return {
'file-open': this.isBlob && this.file.opened,
'file-active': this.isBlob && this.file.active,
folder: this.isTree,
};
},
},
updated() {
if (this.file.type === 'blob' && this.file.active) {
this.$el.scrollIntoView();
}
},
methods: {
...mapActions(['toggleTreeOpen', 'updateDelayViewerUpdated']),
clickFile() {
// Manual Action if a tree is selected/opened
if (
this.isTree &&
this.$router.currentRoute.path === `/project${this.file.url}`
) {
this.toggleTreeOpen(this.file.path);
}
const delayPromise = this.file.changed ?
Promise.resolve() : this.updateDelayViewerUpdated(true);
const delayPromise = this.file.changed
? Promise.resolve()
: this.updateDelayViewerUpdated(true);
return delayPromise.then(() => {
this.$router.push(`/project${row.url}`);
});
},
return delayPromise.then(() => {
router.push(`/project${this.file.url}`);
});
},
};
},
};
</script>
<template>
......@@ -101,54 +82,45 @@
>
<div
class="file-name"
@click="clickFile(file)"
@click="clickFile"
role="button"
>
<a
<span
class="ide-file-name str-truncated"
:style="levelIndentation"
>
<file-icon
:file-name="file.name"
:loading="file.loading"
:folder="file.type === 'tree'"
:folder="isTree"
:opened="file.opened"
:style="levelIndentation"
:size="16"
/>
{{ file.name }}
<file-status-icon :file="file" />
</a>
<file-status-icon
:file="file"
/>
</span>
<changed-file-icon
:file="file"
v-if="file.changed || file.tempFile"
class="prepend-top-5 pull-right"
/>
<new-dropdown
v-if="isTree"
:project-id="file.projectId"
:branch="file.branchId"
:path="file.path"
:parent="file"
/>
<changed-file-icon
:file="file"
v-if="file.changed || file.tempFile"
class="prepend-top-5"
class="pull-right prepend-left-8"
/>
<template v-if="isSubmodule && file.id">
@
<span class="commit-sha">
<a
@click.stop
:href="file.tree_url"
>
{{ shortId }}
</a>
</span>
</template>
</div>
</div>
<template
v-if="file.opened"
>
<template v-if="file.opened">
<repo-file
v-for="childFile in file.tree"
:key="childFile.key"
:file="childFile"
:level="level + 1"
/>
</template>
</div>
......
<script>
import { mapGetters } from 'vuex';
export default {
props: {
file: {
type: Object,
required: true,
},
},
computed: {
...mapGetters([
'activeFile',
]),
showButtons() {
return this.activeFile.rawPath ||
this.activeFile.blamePath ||
this.activeFile.commitsPath ||
this.activeFile.permalink;
return this.file.rawPath ||
this.file.blamePath ||
this.file.commitsPath ||
this.file.permalink;
},
rawDownloadButtonLabel() {
return this.activeFile.binary ? 'Download' : 'Raw';
return this.file.binary ? 'Download' : 'Raw';
},
},
};
......@@ -25,7 +26,7 @@ export default {
class="multi-file-editor-btn-group"
>
<a
:href="activeFile.rawPath"
:href="file.rawPath"
target="_blank"
class="btn btn-default btn-sm raw"
rel="noopener noreferrer">
......@@ -38,19 +39,19 @@ export default {
aria-label="File actions"
>
<a
:href="activeFile.blamePath"
:href="file.blamePath"
class="btn btn-default btn-sm blame"
>
Blame
</a>
<a
:href="activeFile.commitsPath"
:href="file.commitsPath"
class="btn btn-default btn-sm history"
>
History
</a>
<a
:href="activeFile.permalink"
:href="file.permalink"
class="btn btn-default btn-sm permalink"
>
Permalink
......
<script>
import $ from 'jquery';
import { mapGetters } from 'vuex';
import LineHighlighter from '~/line_highlighter';
import syntaxHighlight from '~/syntax_highlight';
export default {
computed: {
...mapGetters([
'activeFile',
]),
renderErrorTooLarge() {
return this.activeFile.renderError === 'too_large';
},
},
mounted() {
this.highlightFile();
this.lineHighlighter = new LineHighlighter({
fileHolderSelector: '.blob-viewer-container',
scrollFileHolder: true,
});
},
updated() {
this.$nextTick(() => {
this.highlightFile();
});
},
methods: {
highlightFile() {
syntaxHighlight($(this.$el).find('.file-content'));
},
},
};
</script>
<template>
<div>
<div
v-if="!activeFile.renderError"
v-html="activeFile.html"
class="multi-file-preview-holder"
>
</div>
<div
v-else-if="activeFile.tempFile"
class="vertical-center render-error">
<p class="text-center">
The source could not be displayed for this temporary file.
</p>
</div>
<div
v-else-if="renderErrorTooLarge"
class="vertical-center render-error">
<p class="text-center">
The source could not be displayed because it is too large.
You can <a
:href="activeFile.rawPath"
download>download</a> it instead.
</p>
</div>
<div
v-else
class="vertical-center render-error">
<p class="text-center">
The source could not be displayed because a rendering error occurred.
You can <a
:href="activeFile.rawPath"
download>download</a> it instead.
</p>
</div>
</div>
</template>
......@@ -67,7 +67,7 @@
<button
type="button"
class="multi-file-tab-close"
@click.stop.prevent="closeFile(tab)"
@click.stop.prevent="closeFile(tab.path)"
:aria-label="closeLabel"
>
<icon
......
<script>
import { mapActions, mapGetters, mapState } from 'vuex';
import { mapActions } from 'vuex';
import RepoTab from './repo_tab.vue';
import EditorMode from './editor_mode_dropdown.vue';
......@@ -8,29 +8,33 @@
RepoTab,
EditorMode,
},
props: {
files: {
type: Array,
required: true,
},
viewer: {
type: String,
required: true,
},
hasChanges: {
type: Boolean,
required: true,
},
},
data() {
return {
showShadow: false,
};
},
computed: {
...mapGetters([
'hasChanges',
]),
...mapState([
'openFiles',
'viewer',
]),
},
updated() {
if (!this.$refs.tabsScroller) return;
this.showShadow = this.$refs.tabsScroller.scrollWidth > this.$refs.tabsScroller.offsetWidth;
this.showShadow =
this.$refs.tabsScroller.scrollWidth > this.$refs.tabsScroller.offsetWidth;
},
methods: {
...mapActions([
'updateViewer',
]),
...mapActions(['updateViewer']),
},
};
</script>
......@@ -42,7 +46,7 @@
ref="tabsScroller"
>
<repo-tab
v-for="tab in openFiles"
v-for="tab in files"
:key="tab.key"
:tab="tab"
/>
......
Поддерживает Markdown
0% или .
You are about to add 0 people to the discussion. Proceed with caution.
Сначала завершите редактирование этого сообщения!
Пожалуйста, зарегистрируйтесь или чтобы прокомментировать