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:
commit
10e3342a06
9 changed files with 124 additions and 34 deletions
|
@ -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]}`;
|
||||||
|
|
|
@ -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() {
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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.
|
||||||
|
|
|
@ -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');
|
||||||
|
|
||||||
|
|
|
@ -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, '/');
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -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();
|
||||||
|
|
Loading…
Reference in a new issue