Merge branch 'ide-fix-back-btn' into 'master'

Repo Editor : Fixes Back Button for files + line number jumping for preview and editor

See merge request gitlab-org/gitlab-ce!14508
This commit is contained in:
Fatih Acet 2017-10-06 17:39:32 +00:00
commit 10e3342a06
9 changed files with 124 additions and 34 deletions

View File

@ -54,12 +54,14 @@ LineHighlighter.prototype.bindEvents = function() {
$fileHolder.on('highlight:line', this.highlightHash); $fileHolder.on('highlight:line', this.highlightHash);
}; };
LineHighlighter.prototype.highlightHash = function() { LineHighlighter.prototype.highlightHash = function(newHash) {
var range; let range;
if (newHash && typeof newHash === 'string') this._hash = newHash;
this.clearHighlight();
if (this._hash !== '') { if (this._hash !== '') {
range = this.hashToRange(this._hash); range = this.hashToRange(this._hash);
if (range[0]) { if (range[0]) {
this.highlightRange(range); this.highlightRange(range);
const lineSelector = `#L${range[0]}`; const lineSelector = `#L${range[0]}`;

View File

@ -63,12 +63,7 @@ const RepoEditor = {
const lineNumber = e.target.position.lineNumber; const lineNumber = e.target.position.lineNumber;
if (e.target.element.classList.contains('line-numbers')) { if (e.target.element.classList.contains('line-numbers')) {
location.hash = `L${lineNumber}`; location.hash = `L${lineNumber}`;
Store.activeLine = lineNumber; Store.setActiveLine(lineNumber);
Helper.monacoInstance.setPosition({
lineNumber: this.activeLine,
column: 1,
});
} }
}, },
}, },
@ -101,6 +96,15 @@ const RepoEditor = {
this.setupEditor(); this.setupEditor();
} }
}, },
activeLine() {
if (Helper.monacoInstance) {
Helper.monacoInstance.setPosition({
lineNumber: this.activeLine,
column: 1,
});
}
},
}, },
computed: { computed: {
shouldHideEditor() { shouldHideEditor() {

View File

@ -14,6 +14,11 @@ export default {
highlightFile() { highlightFile() {
$(this.$el).find('.file-content').syntaxHighlight(); $(this.$el).find('.file-content').syntaxHighlight();
}, },
highlightLine() {
if (Store.activeLine > -1) {
this.lineHighlighter.highlightHash(`#L${Store.activeLine}`);
}
},
}, },
mounted() { mounted() {
this.highlightFile(); this.highlightFile();
@ -26,8 +31,12 @@ export default {
html() { html() {
this.$nextTick(() => { this.$nextTick(() => {
this.highlightFile(); this.highlightFile();
this.highlightLine();
}); });
}, },
activeLine() {
this.highlightLine();
},
}, },
}; };
</script> </script>

View File

@ -18,22 +18,40 @@ export default {
}, },
created() { created() {
this.addPopEventListener(); window.addEventListener('popstate', this.checkHistory);
},
destroyed() {
window.removeEventListener('popstate', this.checkHistory);
}, },
data: () => Store, data: () => Store,
methods: { methods: {
addPopEventListener() { checkHistory() {
window.addEventListener('popstate', () => { let selectedFile = this.files.find(file => location.pathname.indexOf(file.url) > -1);
if (location.href.indexOf('#') > -1) return; if (!selectedFile) {
this.linkClicked({ // Maybe it is not in the current tree but in the opened tabs
selectedFile = Helper.getFileFromPath(location.pathname);
}
let lineNumber = null;
if (location.hash.indexOf('#L') > -1) lineNumber = Number(location.hash.substr(2));
if (selectedFile) {
if (selectedFile.url !== this.activeFile.url) {
this.fileClicked(selectedFile, lineNumber);
} else {
Store.setActiveLine(lineNumber);
}
} else {
// Not opened at all lets open new tab
this.fileClicked({
url: location.href, url: location.href,
}); }, lineNumber);
}); }
}, },
fileClicked(clickedFile) { fileClicked(clickedFile, lineNumber) {
let file = clickedFile; let file = clickedFile;
if (file.loading) return; if (file.loading) return;
file.loading = true; file.loading = true;
@ -41,17 +59,20 @@ export default {
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;
Store.setActiveLine(lineNumber);
} else { } else {
const openFile = Helper.getFileFromPath(file.url); const openFile = Helper.getFileFromPath(file.url);
if (openFile) { if (openFile) {
file.loading = false; file.loading = false;
Store.setActiveFiles(openFile); Store.setActiveFiles(openFile);
Store.setActiveLine(lineNumber);
} else { } else {
Service.url = file.url; Service.url = file.url;
Helper.getContent(file) Helper.getContent(file)
.then(() => { .then(() => {
file.loading = false; file.loading = false;
Helper.scrollTabsRight(); Helper.scrollTabsRight();
Store.setActiveLine(lineNumber);
}) })
.catch(Helper.loadingError); .catch(Helper.loadingError);
} }

View File

@ -254,7 +254,9 @@ const RepoHelper = {
RepoHelper.key = RepoHelper.genKey(); RepoHelper.key = RepoHelper.genKey();
if (document.location.pathname !== url) {
history.pushState({ key: RepoHelper.key }, '', url); history.pushState({ key: RepoHelper.key }, '', url);
}
if (title) { if (title) {
document.title = title; document.title = title;

View File

@ -26,7 +26,7 @@ const RepoStore = {
}, },
activeFile: Helper.getDefaultActiveFile(), activeFile: Helper.getDefaultActiveFile(),
activeFileIndex: 0, activeFileIndex: 0,
activeLine: 0, activeLine: -1,
activeFileLabel: 'Raw', activeFileLabel: 'Raw',
files: [], files: [],
isCommitable: false, isCommitable: false,
@ -85,6 +85,7 @@ const RepoStore = {
if (!file.loading) Helper.updateHistoryEntry(file.url, file.pageTitle || file.name); if (!file.loading) Helper.updateHistoryEntry(file.url, file.pageTitle || file.name);
RepoStore.binary = file.binary; RepoStore.binary = file.binary;
RepoStore.setActiveLine(-1);
}, },
setFileActivity(file, openedFile, i) { setFileActivity(file, openedFile, i) {
@ -101,6 +102,10 @@ const RepoStore = {
RepoStore.activeFileIndex = i; RepoStore.activeFileIndex = i;
}, },
setActiveLine(activeLine) {
if (!isNaN(activeLine)) RepoStore.activeLine = activeLine;
},
setActiveToRaw() { setActiveToRaw() {
RepoStore.activeFile.raw = false; RepoStore.activeFile.raw = false;
// can't get vue to listen to raw for some reason so RepoStore for now. // can't get vue to listen to raw for some reason so RepoStore for now.

View File

@ -29,15 +29,17 @@ describe('RepoFile', () => {
}).$mount(); }).$mount();
} }
beforeEach(() => {
spyOn(repoFile.mixins[0].methods, 'timeFormated').and.returnValue(updated);
});
it('renders link, icon, name and last commit details', () => { it('renders link, icon, name and last commit details', () => {
const vm = createComponent({ const RepoFile = Vue.extend(repoFile);
const vm = new RepoFile({
propsData: {
file, file,
activeFile, activeFile,
},
}); });
spyOn(vm, 'timeFormated').and.returnValue(updated);
vm.$mount();
const name = vm.$el.querySelector('.repo-file-name'); const name = vm.$el.querySelector('.repo-file-name');
const fileIcon = vm.$el.querySelector('.file-icon'); const fileIcon = vm.$el.querySelector('.file-icon');

View File

@ -5,18 +5,26 @@ import RepoStore from '~/repo/stores/repo_store';
import repoSidebar from '~/repo/components/repo_sidebar.vue'; import repoSidebar from '~/repo/components/repo_sidebar.vue';
describe('RepoSidebar', () => { describe('RepoSidebar', () => {
let vm;
function createComponent() { function createComponent() {
const RepoSidebar = Vue.extend(repoSidebar); const RepoSidebar = Vue.extend(repoSidebar);
return new RepoSidebar().$mount(); return new RepoSidebar().$mount();
} }
afterEach(() => {
vm.$destroy();
});
it('renders a sidebar', () => { it('renders a sidebar', () => {
RepoStore.files = [{ RepoStore.files = [{
id: 0, id: 0,
}]; }];
RepoStore.openedFiles = []; RepoStore.openedFiles = [];
const vm = createComponent(); RepoStore.isRoot = false;
vm = createComponent();
const thead = vm.$el.querySelector('thead'); const thead = vm.$el.querySelector('thead');
const tbody = vm.$el.querySelector('tbody'); const tbody = vm.$el.querySelector('tbody');
@ -35,7 +43,7 @@ describe('RepoSidebar', () => {
RepoStore.openedFiles = [{ RepoStore.openedFiles = [{
id: 0, id: 0,
}]; }];
const vm = createComponent(); vm = createComponent();
expect(vm.$el.classList.contains('sidebar-mini')).toBeTruthy(); expect(vm.$el.classList.contains('sidebar-mini')).toBeTruthy();
expect(vm.$el.querySelector('thead')).toBeFalsy(); expect(vm.$el.querySelector('thead')).toBeFalsy();
@ -47,7 +55,7 @@ describe('RepoSidebar', () => {
tree: true, tree: true,
}; };
RepoStore.files = []; RepoStore.files = [];
const vm = createComponent(); vm = createComponent();
expect(vm.$el.querySelectorAll('tbody .loading-file').length).toEqual(5); expect(vm.$el.querySelectorAll('tbody .loading-file').length).toEqual(5);
}); });
@ -57,7 +65,7 @@ describe('RepoSidebar', () => {
id: 0, id: 0,
}]; }];
RepoStore.isRoot = true; RepoStore.isRoot = true;
const vm = createComponent(); vm = createComponent();
expect(vm.$el.querySelector('tbody .prev-directory')).toBeTruthy(); expect(vm.$el.querySelector('tbody .prev-directory')).toBeTruthy();
}); });
@ -72,7 +80,7 @@ describe('RepoSidebar', () => {
}; };
RepoStore.files = [file1]; RepoStore.files = [file1];
RepoStore.isRoot = true; RepoStore.isRoot = true;
const vm = createComponent(); vm = createComponent();
vm.fileClicked(file1); vm.fileClicked(file1);
@ -87,7 +95,7 @@ describe('RepoSidebar', () => {
spyOn(Helper, 'getFileFromPath').and.returnValue(file); spyOn(Helper, 'getFileFromPath').and.returnValue(file);
spyOn(RepoStore, 'setActiveFiles'); spyOn(RepoStore, 'setActiveFiles');
const vm = createComponent(); vm = createComponent();
vm.fileClicked(file); vm.fileClicked(file);
expect(RepoStore.setActiveFiles).toHaveBeenCalledWith(file); expect(RepoStore.setActiveFiles).toHaveBeenCalledWith(file);
@ -103,7 +111,7 @@ describe('RepoSidebar', () => {
}; };
RepoStore.files = [file1]; RepoStore.files = [file1];
RepoStore.isRoot = true; RepoStore.isRoot = true;
const vm = createComponent(); vm = createComponent();
vm.fileClicked(file1); vm.fileClicked(file1);
@ -114,12 +122,48 @@ describe('RepoSidebar', () => {
describe('goToPreviousDirectoryClicked', () => { describe('goToPreviousDirectoryClicked', () => {
it('should hide files in directory if already open', () => { it('should hide files in directory if already open', () => {
const prevUrl = 'foo/bar'; const prevUrl = 'foo/bar';
const vm = createComponent(); vm = createComponent();
vm.goToPreviousDirectoryClicked(prevUrl); vm.goToPreviousDirectoryClicked(prevUrl);
expect(RepoService.url).toEqual(prevUrl); expect(RepoService.url).toEqual(prevUrl);
}); });
}); });
describe('back button', () => {
const file1 = {
id: 1,
url: 'file1',
};
const file2 = {
id: 2,
url: 'file2',
};
RepoStore.files = [file1, file2];
RepoStore.openedFiles = [file1, file2];
RepoStore.isRoot = true;
vm = createComponent();
vm.fileClicked(file1);
it('render previous file when using back button', () => {
spyOn(Helper, 'getContent').and.callThrough();
vm.fileClicked(file2);
expect(Helper.getContent).toHaveBeenCalledWith(file2);
Helper.getContent.calls.reset();
history.pushState({
key: Math.random(),
}, '', file1.url);
const popEvent = document.createEvent('Event');
popEvent.initEvent('popstate', true, true);
window.dispatchEvent(popEvent);
expect(Helper.getContent.calls.mostRecent().args[0].url).toContain(file1.url);
window.history.pushState({}, null, '/');
});
});
}); });
}); });

View File

@ -31,6 +31,7 @@ describe('MRWidgetService', () => {
}); });
it('should have methods defined', () => { it('should have methods defined', () => {
window.history.pushState({}, null, '/');
const service = new MRWidgetService(mr); const service = new MRWidgetService(mr);
expect(service.merge()).toBeDefined(); expect(service.merge()).toBeDefined();