Commit 574f6a58 authored by Jacob Schatz's avatar Jacob Schatz Committed by Simon Knox

Merge branch 'repo-fixes-e' into 'master'

Repo fixes part E

See merge request !13472
parent da18aa8e
...@@ -29,12 +29,10 @@ export default { ...@@ -29,12 +29,10 @@ export default {
editMode() { editMode() {
if (this.editMode) { if (this.editMode) {
$('.project-refs-form').addClass('disabled'); $('.project-refs-form').addClass('disabled');
$('.fa-long-arrow-right').show(); $('.js-tree-ref-target-holder').show();
$('.project-refs-target-form').show();
} else { } else {
$('.project-refs-form').removeClass('disabled'); $('.project-refs-form').removeClass('disabled');
$('.fa-long-arrow-right').hide(); $('.js-tree-ref-target-holder').hide();
$('.project-refs-target-form').hide();
} }
}, },
}, },
......
...@@ -4,7 +4,7 @@ import Store from '../stores/repo_store'; ...@@ -4,7 +4,7 @@ import Store from '../stores/repo_store';
export default { export default {
data: () => Store, data: () => Store,
mounted() { mounted() {
$(this.$el).find('.file-content').syntaxHighlight(); this.highlightFile();
}, },
computed: { computed: {
html() { html() {
...@@ -12,10 +12,16 @@ export default { ...@@ -12,10 +12,16 @@ export default {
}, },
}, },
methods: {
highlightFile() {
$(this.$el).find('.file-content').syntaxHighlight();
},
},
watch: { watch: {
html() { html() {
this.$nextTick(() => { this.$nextTick(() => {
$(this.$el).find('.file-content').syntaxHighlight(); this.highlightFile();
}); });
}, },
}, },
...@@ -24,9 +30,23 @@ export default { ...@@ -24,9 +30,23 @@ export default {
<template> <template>
<div> <div>
<div v-if="!activeFile.render_error" v-html="activeFile.html"></div> <div
<div v-if="activeFile.render_error" class="vertical-center render-error"> v-if="!activeFile.render_error"
<p class="text-center">The source could not be displayed because it is too large. You can <a :href="activeFile.raw_path">download</a> it instead.</p> v-html="activeFile.html">
</div>
<div
v-else-if="activeFile.tooLarge"
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.raw_path">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 occured. You can <a :href="activeFile.raw_path">download</a> it instead.
</p>
</div> </div>
</div> </div>
</template> </template>
...@@ -33,32 +33,30 @@ const RepoSidebar = { ...@@ -33,32 +33,30 @@ const RepoSidebar = {
}); });
}, },
linkClicked(clickedFile) { fileClicked(clickedFile) {
let url = '';
let file = clickedFile; let file = clickedFile;
if (typeof file === 'object') {
file.loading = true; file.loading = true;
if (file.type === 'tree' && file.opened) { if (file.type === 'tree' && file.opened) {
file = Store.removeChildFilesOfTree(file); file = Store.removeChildFilesOfTree(file);
file.loading = false; file.loading = false;
} else { } else {
url = file.url; Service.url = file.url;
Service.url = url; Helper.getContent(file)
// I need to refactor this to do the `then` here. .then(() => {
// Not a callback. For now this is good enough.
// it works.
Helper.getContent(file, () => {
file.loading = false; file.loading = false;
Helper.scrollTabsRight(); Helper.scrollTabsRight();
}); })
} .catch(Helper.loadingError);
} else if (typeof file === 'string') {
// go back
url = file;
Service.url = url;
Helper.getContent(null, () => Helper.scrollTabsRight());
} }
}, },
goToPreviousDirectoryClicked(prevURL) {
Service.url = prevURL;
Helper.getContent(null)
.then(() => Helper.scrollTabsRight())
.catch(Helper.loadingError);
},
}, },
}; };
...@@ -82,7 +80,7 @@ export default RepoSidebar; ...@@ -82,7 +80,7 @@ export default RepoSidebar;
<repo-previous-directory <repo-previous-directory
v-if="isRoot" v-if="isRoot"
:prev-url="prevURL" :prev-url="prevURL"
@linkclicked="linkClicked(prevURL)"/> @linkclicked="goToPreviousDirectoryClicked(prevURL)"/>
<repo-loading-file <repo-loading-file
v-for="n in 5" v-for="n in 5"
:key="n" :key="n"
...@@ -94,7 +92,7 @@ export default RepoSidebar; ...@@ -94,7 +92,7 @@ export default RepoSidebar;
:key="file.id" :key="file.id"
:file="file" :file="file"
:is-mini="isMini" :is-mini="isMini"
@linkclicked="linkClicked(file)" @linkclicked="fileClicked(file)"
:is-tree="isTree" :is-tree="isTree"
:has-files="!!files.length" :has-files="!!files.length"
:active-file="activeFile"/> :active-file="activeFile"/>
......
...@@ -10,6 +10,12 @@ const RepoTab = { ...@@ -10,6 +10,12 @@ const RepoTab = {
}, },
computed: { computed: {
closeLabel() {
if (this.tab.changed) {
return `${this.tab.name} changed`;
}
return `Close ${this.tab.name}`;
},
changedClass() { changedClass() {
const tabChangedObj = { const tabChangedObj = {
'fa-times': !this.tab.changed, 'fa-times': !this.tab.changed,
...@@ -34,12 +40,24 @@ export default RepoTab; ...@@ -34,12 +40,24 @@ export default RepoTab;
<template> <template>
<li> <li>
<a href="#" class="close" @click.prevent="xClicked(tab)" v-if="!tab.loading"> <a
<i class="fa" :class="changedClass"></i> href="#0"
class="close"
@click.prevent="xClicked(tab)"
:aria-label="closeLabel">
<i
class="fa"
:class="changedClass"
aria-hidden="true">
</i>
</a> </a>
<a href="#" class="repo-tab" v-if="!tab.loading" :title="tab.url" @click.prevent="tabClicked(tab)">{{tab.name}}</a> <a
href="#"
<i v-if="tab.loading" class="fa fa-spinner fa-spin"></i> class="repo-tab"
:title="tab.url"
@click.prevent="tabClicked(tab)">
{{tab.name}}
</a>
</li> </li>
</template> </template>
<script> <script>
import Vue from 'vue';
import Store from '../stores/repo_store'; import Store from '../stores/repo_store';
import RepoTab from './repo_tab.vue'; import RepoTab from './repo_tab.vue';
import RepoMixin from '../mixins/repo_mixin'; import RepoMixin from '../mixins/repo_mixin';
...@@ -14,29 +13,19 @@ const RepoTabs = { ...@@ -14,29 +13,19 @@ const RepoTabs = {
data: () => Store, data: () => Store,
methods: { methods: {
isOverflow() {
return this.$el.scrollWidth > this.$el.offsetWidth;
},
xClicked(file) { xClicked(file) {
Store.removeFromOpenedFiles(file); Store.removeFromOpenedFiles(file);
}, },
}, },
watch: {
openedFiles() {
Vue.nextTick(() => {
this.tabsOverflow = this.isOverflow();
});
},
},
}; };
export default RepoTabs; export default RepoTabs;
</script> </script>
<template> <template>
<ul id="tabs" v-if="isMini" v-cloak :class="{'overflown': tabsOverflow}"> <ul
v-if="isMini"
id="tabs">
<repo-tab v-for="tab in openedFiles" :key="tab.id" :tab="tab" :class="{'active' : tab.active}" @xclicked="xClicked"/> <repo-tab v-for="tab in openedFiles" :key="tab.id" :tab="tab" :class="{'active' : tab.active}" @xclicked="xClicked"/>
<li class="tabs-divider" /> <li class="tabs-divider" />
</ul> </ul>
......
...@@ -10,7 +10,10 @@ function repoEditorLoader() { ...@@ -10,7 +10,10 @@ function repoEditorLoader() {
Store.monaco = monaco; Store.monaco = monaco;
Store.monacoLoading = false; Store.monacoLoading = false;
resolve(RepoEditor); resolve(RepoEditor);
}, reject); }, () => {
Store.monacoLoading = false;
reject();
});
}); });
} }
......
...@@ -33,12 +33,16 @@ const RepoHelper = { ...@@ -33,12 +33,16 @@ const RepoHelper = {
? window.performance ? window.performance
: Date, : Date,
getFileExtension(fileName) {
return fileName.split('.').pop();
},
getBranch() { getBranch() {
return $('button.dropdown-menu-toggle').attr('data-ref'); return $('button.dropdown-menu-toggle').attr('data-ref');
}, },
getLanguageIDForFile(file, langs) { getLanguageIDForFile(file, langs) {
const ext = file.name.split('.').pop(); const ext = RepoHelper.getFileExtension(file.name);
const foundLang = RepoHelper.findLanguage(ext, langs); const foundLang = RepoHelper.findLanguage(ext, langs);
return foundLang ? foundLang.id : 'plaintext'; return foundLang ? foundLang.id : 'plaintext';
...@@ -135,21 +139,19 @@ const RepoHelper = { ...@@ -135,21 +139,19 @@ const RepoHelper = {
return isRoot; return isRoot;
}, },
getContent(treeOrFile, cb) { getContent(treeOrFile) {
let file = treeOrFile; let file = treeOrFile;
// const loadingData = RepoHelper.setLoading(true); // const loadingData = RepoHelper.setLoading(true);
return Service.getContent() return Service.getContent()
.then((response) => { .then((response) => {
const data = response.data; const data = response.data;
// RepoHelper.setLoading(false, loadingData); // RepoHelper.setLoading(false, loadingData);
if (cb) cb();
Store.isTree = RepoHelper.isTree(data); Store.isTree = RepoHelper.isTree(data);
if (!Store.isTree) { if (!Store.isTree) {
if (!file) file = data; if (!file) file = data;
Store.binary = data.binary; Store.binary = data.binary;
if (data.binary) { if (data.binary) {
Store.binaryMimeType = data.mime_type;
// file might be undefined // file might be undefined
RepoHelper.setBinaryDataAsBase64(data); RepoHelper.setBinaryDataAsBase64(data);
Store.setViewToPreview(); Store.setViewToPreview();
...@@ -188,9 +190,8 @@ const RepoHelper = { ...@@ -188,9 +190,8 @@ const RepoHelper = {
setFile(data, file) { setFile(data, file) {
const newFile = data; const newFile = data;
newFile.url = file.url || location.pathname;
newFile.url = file.url; newFile.url = file.url;
if (newFile.render_error === 'too_large') { if (newFile.render_error === 'too_large' || newFile.render_error === 'collapsed') {
newFile.tooLarge = true; newFile.tooLarge = true;
} }
newFile.newContent = ''; newFile.newContent = '';
...@@ -199,10 +200,6 @@ const RepoHelper = { ...@@ -199,10 +200,6 @@ const RepoHelper = {
Store.setActiveFiles(newFile); Store.setActiveFiles(newFile);
}, },
toFA(icon) {
return `fa-${icon}`;
},
serializeBlob(blob) { serializeBlob(blob) {
const simpleBlob = RepoHelper.serializeRepoEntity('blob', blob); const simpleBlob = RepoHelper.serializeRepoEntity('blob', blob);
simpleBlob.lastCommitMessage = blob.last_commit.message; simpleBlob.lastCommitMessage = blob.last_commit.message;
...@@ -226,7 +223,7 @@ const RepoHelper = { ...@@ -226,7 +223,7 @@ const RepoHelper = {
type, type,
name, name,
url, url,
icon: RepoHelper.toFA(icon), icon: `fa-${icon}`,
level: 0, level: 0,
loading: false, loading: false,
}; };
...@@ -244,7 +241,7 @@ const RepoHelper = { ...@@ -244,7 +241,7 @@ const RepoHelper = {
setTimeout(() => { setTimeout(() => {
const tabs = document.getElementById('tabs'); const tabs = document.getElementById('tabs');
if (!tabs) return; if (!tabs) return;
tabs.scrollLeft = 12000; tabs.scrollLeft = tabs.scrollWidth;
}, 200); }, 200);
}, },
......
...@@ -7,8 +7,7 @@ import RepoEditButton from './components/repo_edit_button.vue'; ...@@ -7,8 +7,7 @@ import RepoEditButton from './components/repo_edit_button.vue';
import Translate from '../vue_shared/translate'; import Translate from '../vue_shared/translate';
function initDropdowns() { function initDropdowns() {
$('.project-refs-target-form').hide(); $('.js-tree-ref-target-holder').hide();
$('.fa-long-arrow-right').hide();
} }
function addEventsForNonVueEls() { function addEventsForNonVueEls() {
......
...@@ -2,6 +2,7 @@ ...@@ -2,6 +2,7 @@
import axios from 'axios'; import axios from 'axios';
import Store from '../stores/repo_store'; import Store from '../stores/repo_store';
import Api from '../../api'; import Api from '../../api';
import Helper from '../helpers/repo_helper';
const RepoService = { const RepoService = {
url: '', url: '',
...@@ -22,6 +23,7 @@ const RepoService = { ...@@ -22,6 +23,7 @@ const RepoService = {
getRaw(url) { getRaw(url) {
return axios.get(url, { return axios.get(url, {
// Stop Axios from parsing a JSON file into a JS object
transformResponse: [res => res], transformResponse: [res => res],
}); });
}, },
...@@ -36,7 +38,7 @@ const RepoService = { ...@@ -36,7 +38,7 @@ const RepoService = {
}, },
urlIsRichBlob(url = this.url) { urlIsRichBlob(url = this.url) {
const extension = url.split('.').pop(); const extension = Helper.getFileExtension(url);
return this.richExtensionRegExp.test(extension); return this.richExtensionRegExp.test(extension);
}, },
......
...@@ -3,13 +3,10 @@ import Helper from '../helpers/repo_helper'; ...@@ -3,13 +3,10 @@ import Helper from '../helpers/repo_helper';
import Service from '../services/repo_service'; import Service from '../services/repo_service';
const RepoStore = { const RepoStore = {
ideEl: {},
monaco: {}, monaco: {},
monacoLoading: false, monacoLoading: false,
monacoInstance: {}, monacoInstance: {},
service: '', service: '',
editor: '',
sidebar: '',
editMode: false, editMode: false,
isTree: false, isTree: false,
isRoot: false, isRoot: false,
...@@ -17,19 +14,10 @@ const RepoStore = { ...@@ -17,19 +14,10 @@ const RepoStore = {
projectId: '', projectId: '',
projectName: '', projectName: '',
projectUrl: '', projectUrl: '',
trees: [],
blobs: [],
submodules: [],
blobRaw: '', blobRaw: '',
blobRendered: '',
currentBlobView: 'repo-preview', currentBlobView: 'repo-preview',
openedFiles: [], openedFiles: [],
tabSize: 100,
defaultTabSize: 100,
minTabSize: 30,
tabsOverflow: 41,
submitCommitsLoading: false, submitCommitsLoading: false,
binaryLoaded: false,
dialog: { dialog: {
open: false, open: false,
title: '', title: '',
...@@ -45,9 +33,6 @@ const RepoStore = { ...@@ -45,9 +33,6 @@ const RepoStore = {
currentBranch: '', currentBranch: '',
targetBranch: 'new-branch', targetBranch: 'new-branch',
commitMessage: '', commitMessage: '',
binaryMimeType: '',
// scroll bar space for windows
scrollWidth: 0,
binaryTypes: { binaryTypes: {
png: false, png: false,
md: false, md: false,
...@@ -58,7 +43,6 @@ const RepoStore = { ...@@ -58,7 +43,6 @@ const RepoStore = {
tree: false, tree: false,
blob: false, blob: false,
}, },
readOnly: true,
resetBinaryTypes() { resetBinaryTypes() {
Object.keys(RepoStore.binaryTypes).forEach((key) => { Object.keys(RepoStore.binaryTypes).forEach((key) => {
...@@ -96,7 +80,6 @@ const RepoStore = { ...@@ -96,7 +80,6 @@ const RepoStore = {
if (file.binary) { if (file.binary) {
RepoStore.blobRaw = file.base64; RepoStore.blobRaw = file.base64;
RepoStore.binaryMimeType = file.mime_type;
} else if (file.newContent || file.plain) { } else if (file.newContent || file.plain) {
RepoStore.blobRaw = file.newContent || file.plain; RepoStore.blobRaw = file.newContent || file.plain;
} else { } else {
...@@ -238,4 +221,5 @@ const RepoStore = { ...@@ -238,4 +221,5 @@ const RepoStore = {
return RepoStore.currentBlobView === 'repo-preview'; return RepoStore.currentBlobView === 'repo-preview';
}, },
}; };
export default RepoStore; export default RepoStore;
...@@ -29,6 +29,10 @@ ...@@ -29,6 +29,10 @@
margin-right: 15px; margin-right: 15px;
} }
.tree-ref-target-holder {
display: inline-block;
}
.repo-breadcrumb { .repo-breadcrumb {
li:last-of-type { li:last-of-type {
position: relative; position: relative;
......
...@@ -2,8 +2,9 @@ ...@@ -2,8 +2,9 @@
.tree-ref-holder .tree-ref-holder
= render 'shared/ref_switcher', destination: 'tree', path: @path = render 'shared/ref_switcher', destination: 'tree', path: @path
- if show_new_repo? - if show_new_repo?
= icon('long-arrow-right', title: 'to target branch') .tree-ref-target-holder.js-tree-ref-target-holder
= render 'shared/target_switcher', destination: 'tree', path: @path = icon('long-arrow-right', title: 'to target branch')
= render 'shared/target_switcher', destination: 'tree', path: @path
- unless show_new_repo? - unless show_new_repo?
= render 'projects/tree/old_tree_header' = render 'projects/tree/old_tree_header'
......
import Vue from 'vue'; import Vue from 'vue';
import Helper from '~/repo/helpers/repo_helper';
import RepoService from '~/repo/services/repo_service';
import RepoStore from '~/repo/stores/repo_store'; import RepoStore from '~/repo/stores/repo_store';
import repoSidebar from '~/repo/components/repo_sidebar.vue'; import repoSidebar from '~/repo/components/repo_sidebar.vue';
...@@ -58,4 +60,51 @@ describe('RepoSidebar', () => { ...@@ -58,4 +60,51 @@ describe('RepoSidebar', () => {
expect(vm.$el.querySelector('tbody .prev-directory')).toBeTruthy(); expect(vm.$el.querySelector('tbody .prev-directory')).toBeTruthy();
}); });
describe('methods', () => {
describe('fileClicked', () => {
it('should fetch data for new file', () => {
spyOn(Helper, 'getContent').and.callThrough();
const file1 = {
id: 0,
url: '',
};
RepoStore.files = [file1];
RepoStore.isRoot = true;
const vm = createComponent();
vm.fileClicked(file1);
expect(Helper.getContent).toHaveBeenCalledWith(file1);
});
it('should hide files in directory if already open', () => {
spyOn(RepoStore, 'removeChildFilesOfTree').and.callThrough();
const file1 = {
id: 0,
type: 'tree',
url: '',
opened: true,
};
RepoStore.files = [file1];
RepoStore.isRoot = true;
const vm = createComponent();
vm.fileClicked(file1);
expect(RepoStore.removeChildFilesOfTree).toHaveBeenCalledWith(file1);
});
});
describe('goToPreviousDirectoryClicked', () => {
it('should hide files in directory if already open', () => {
const prevUrl = 'foo/bar';
const vm = createComponent();
vm.goToPreviousDirectoryClicked(prevUrl);
expect(RepoService.url).toEqual(prevUrl);
});
});
});
}); });
...@@ -12,7 +12,6 @@ describe('RepoTab', () => { ...@@ -12,7 +12,6 @@ describe('RepoTab', () => {
it('renders a close link and a name link', () => { it('renders a close link and a name link', () => {
const tab = { const tab = {
loading: false,
url: 'url', url: 'url',
name: 'name', name: 'name',
}; };
...@@ -26,7 +25,7 @@ describe('RepoTab', () => { ...@@ -26,7 +25,7 @@ describe('RepoTab', () => {
spyOn(vm, 'tabClicked'); spyOn(vm, 'tabClicked');
expect(close.querySelector('.fa-times')).toBeTruthy(); expect(close.querySelector('.fa-times')).toBeTruthy();
expect(name.textContent).toEqual(tab.name); expect(name.textContent.trim()).toEqual(tab.name);
close.click(); close.click();
name.click(); name.click();
...@@ -35,25 +34,8 @@ describe('RepoTab', () => { ...@@ -35,25 +34,8 @@ describe('RepoTab', () => {
expect(vm.tabClicked).toHaveBeenCalledWith(tab); expect(vm.tabClicked).toHaveBeenCalledWith(tab);
}); });
it('renders a spinner if tab is loading', () => {
const tab = {
loading: true,
url: 'url',
};
const vm = createComponent({
tab,
});
const close = vm.$el.querySelector('.close');
const name = vm.$el.querySelector(`a[title="${tab.url}"]`);
expect(close).toBeFalsy();
expect(name).toBeFalsy();
expect(vm.$el.querySelector('.fa.fa-spinner.fa-spin')).toBeTruthy();
});
it('renders an fa-circle icon if tab is changed', () => { it('renders an fa-circle icon if tab is changed', () => {
const tab = { const tab = {
loading: false,
url: 'url', url: 'url',
name: 'name', name: 'name',
changed: true, changed: true,
......
...@@ -18,13 +18,11 @@ describe('RepoTabs', () => { ...@@ -18,13 +18,11 @@ describe('RepoTabs', () => {
it('renders a list of tabs', () => { it('renders a list of tabs', () => {
RepoStore.openedFiles = openedFiles; RepoStore.openedFiles = openedFiles;
RepoStore.tabsOverflow = true;
const vm = createComponent(); const vm = createComponent();
const tabs = [...vm.$el.querySelectorAll(':scope > li')]; const tabs = [...vm.$el.querySelectorAll(':scope > li')];
expect(vm.$el.id).toEqual('tabs'); expect(vm.$el.id).toEqual('tabs');
expect(vm.$el.classList.contains('overflown')).toBeTruthy();
expect(tabs.length).toEqual(3); expect(tabs.length).toEqual(3);
expect(tabs[0].classList.contains('active')).toBeTruthy(); expect(tabs[0].classList.contains('active')).toBeTruthy();
expect(tabs[1].classList.contains('active')).toBeFalsy(); expect(tabs[1].classList.contains('active')).toBeFalsy();
...@@ -39,15 +37,6 @@ describe('RepoTabs', () => { ...@@ -39,15 +37,6 @@ describe('RepoTabs', () => {
expect(vm.$el.innerHTML).toBeFalsy(); expect(vm.$el.innerHTML).toBeFalsy();
}); });
it('does not apply overflown class if not tabsOverflow', () => {
RepoStore.openedFiles = openedFiles;
RepoStore.tabsOverflow = false;
const vm = createComponent();
expect(vm.$el.classList.contains('overflown')).toBeFalsy();
});
describe('methods', () => { describe('methods', () => {
describe('xClicked', () => { describe('xClicked', () => {
it('calls removeFromOpenedFiles with file obj', () => { it('calls removeFromOpenedFiles with file obj', () => {
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment