Commit 3a2f883f authored by Jacob Schatz's avatar Jacob Schatz

Merge branch 'ide' of gitlab.com:gitlab-org/gitlab-ce into ide With conflicts

parents 0b500111 51a936fb
......@@ -72,19 +72,18 @@ import RepoBundle from './repo/repo_bundle';
}
Dispatcher.prototype.initPageScripts = function() {
var page, path, shortcut_handler, fileBlobPermalinkUrlElement, fileBlobPermalinkUrl, os;
var page, path, shortcut_handler, fileBlobPermalinkUrlElement, fileBlobPermalinkUrl;
page = $('body').attr('data-page');
if (!page) {
return false;
}
function getScrollBarWidth () {
var $outer = $('<div>').css({visibility: 'hidden', width: 100, overflow: 'scroll'}).appendTo('body'),
widthWithScroll = $('<div>').css({width: '100%'}).appendTo($outer).outerWidth();
var $outer = $('<div>').css({ visibility: 'hidden', width: 100, overflow: 'scroll' }).appendTo('body'),
widthWithScroll = $('<div>').css({ width: '100%' }).appendTo($outer).outerWidth();
$outer.remove();
return 100 - widthWithScroll;
};
}
$('body').attr('data-scroll-width', getScrollBarWidth());
......
import Vue from 'vue'
import Store from './repo_store'
import Vue from 'vue';
import Store from './repo_store';
import { loadingError } from './repo_helper';
export default class RepoBinaryViewer {
constructor(url) {
constructor() {
this.initVue();
}
......@@ -15,32 +16,51 @@ export default class RepoBinaryViewer {
computed: {
pngBlobWithDataURI() {
return `data:image/png;base64,${this.blobRaw}`;
}
},
},
methods: {
<<<<<<< HEAD
isMarkdown() {
return this.activeFile.extension === 'md';
=======
supportedNonBinaryFileType() {
switch (this.activeFile.extension) {
case 'md':
this.binaryTypes.markdown = true;
return true;
default:
return false;
}
>>>>>>> 51a936fb3d2cdbd133a3b0eed463b47c1c92fe7d
},
},
watch: {
blobRaw() {
<<<<<<< HEAD
if(this.isMarkdown()) {
=======
const supported = this.supportedNonBinaryFileType();
if (supported) {
>>>>>>> 51a936fb3d2cdbd133a3b0eed463b47c1c92fe7d
this.binaryTypes.markdown = true;
this.activeFile.raw = false;
// counts as binaryish so we use the binary viewer in this case.
this.binary = true;
return;
}
if(!this.binary) return;
switch(this.binaryMimeType) {
if (!this.binary) return;
switch (this.binaryMimeType) {
case 'image/png':
this.binaryTypes.png = true;
break;
default:
loadingError();
break;
}
}
}
},
},
});
}
}
<<<<<<< HEAD
import Tabs from './repo_tabs'
import Sidebar from './repo_sidebar'
import Editor from './repo_editor'
......@@ -8,6 +9,16 @@ import CommitSection from './repo_commit_section'
import Service from './repo_service'
import Store from './repo_store'
import Helper from './repo_helper'
=======
import Tabs from './repo_tabs';
import Sidebar from './repo_sidebar';
import Editor from './repo_editor';
import FileButtons from './repo_file_buttons';
import BinaryViewer from './repo_binary_viewer';
import Service from './repo_service';
import Store from './repo_store';
import Helper from './repo_helper';
>>>>>>> 51a936fb3d2cdbd133a3b0eed463b47c1c92fe7d
export default class RepoBundle {
constructor() {
......
/* global monaco */
import Vue from 'vue';
import Store from './repo_store'
import Helper from './repo_helper'
import Store from './repo_store';
import Helper from './repo_helper';
export default class RepoEditor {
constructor() {
......@@ -10,12 +10,19 @@ export default class RepoEditor {
}
addMonacoEvents() {
<<<<<<< HEAD
this.monacoEditor.onMouseUp(this.onMonacoEditorMouseUp.bind(this));
this.monacoEditor.onKeyUp(this.onMonacoEditorKeysPressed.bind(this));
=======
this.vue.$watch('activeFile.lineNumber', () => {
console.log('cahnged');
});
this.monacoEditor.onMouseUp(RepoEditor.onMonacoEditorMouseUp);
>>>>>>> 51a936fb3d2cdbd133a3b0eed463b47c1c92fe7d
}
onMonacoEditorMouseUp(e) {
if(e.target.element.className === 'line-numbers') {
static onMonacoEditorMouseUp(e) {
if (e.target.element.className === 'line-numbers') {
location.hash = `L${e.target.position.lineNumber}`;
Store.activeLine = e.target.position.lineNumber;
}
......@@ -34,7 +41,7 @@ export default class RepoEditor {
model: null,
readOnly: true,
contextmenu: false,
}
},
);
Helper.monacoInstance = monaco;
......@@ -49,33 +56,37 @@ export default class RepoEditor {
const monacoEditor = this.monacoEditor;
this.vue = new Vue({
data: () => Store,
created () {
created() {
this.showHide();
if(this.blobRaw !== ''){
if (this.blobRaw !== '') {
monacoEditor.setModel(
monaco.editor.createModel(
this.blobRaw,
'plain'
)
'plain',
),
);
}
},
methods: {
showHide() {
<<<<<<< HEAD
if(!this.openedFiles.length || (this.binary && !this.activeFile.raw)) {
=======
if ((!this.openedFiles.length) || this.binary) {
>>>>>>> 51a936fb3d2cdbd133a3b0eed463b47c1c92fe7d
self.el.style.display = 'none';
} else {
self.el.style.display = 'inline-block';
}
}
},
},
watch: {
activeLine() {
self.monacoEditor.setPosition({
lineNumber: this.activeLine,
column: 1
column: 1,
});
},
......@@ -114,20 +125,24 @@ export default class RepoEditor {
blobRaw() {
this.showHide();
<<<<<<< HEAD
if(!this.isTree) {
// kill the current model;
self.monacoEditor.setModel(null);
// then create the new one
=======
if (!this.isTree) {
>>>>>>> 51a936fb3d2cdbd133a3b0eed463b47c1c92fe7d
self.monacoEditor.setModel(
monaco.editor.createModel(
this.blobRaw,
this.activeFile.mime_type
)
this.activeFile.mime_type,
),
);
console.log(monaco.editor.getModels());
}
}
}
},
},
});
}
}
let RepoFile = {
const RepoFile = {
template: `
<tr v-if='!loading.tree || hasFiles' :class='{"active": activeFile.url === file.url}'>
<td>
......@@ -20,13 +20,13 @@ let RepoFile = {
isMini: Boolean,
loading: Object,
hasFiles: Boolean,
activeFile: Object
activeFile: Object,
},
methods: {
linkClicked(file) {
this.$emit('linkclicked', file);
}
}
},
},
};
export default RepoFile;
<<<<<<< HEAD
import Vue from 'vue'
import Store from './repo_store'
import Helper from './repo_helper'
import RepoMiniMixin from './repo_mini_mixin'
=======
import Vue from 'vue';
import Store from './repo_store';
import Helper from './repo_helper';
>>>>>>> 51a936fb3d2cdbd133a3b0eed463b47c1c92fe7d
export default class RepoSidebar {
constructor(url) {
......@@ -31,9 +37,14 @@ export default class RepoSidebar {
</div>
`,
computed: {
<<<<<<< HEAD
editableBorder() {
return this.editMode ? '1px solid #1F78D1' :'1px solid #f0f0f0';
=======
previewLabel() {
return this.activeFile.raw ? 'Preview' : 'Raw';
>>>>>>> 51a936fb3d2cdbd133a3b0eed463b47c1c92fe7d
},
canPreview() {
......@@ -50,13 +61,19 @@ export default class RepoSidebar {
historyFileUrl() {
return Helper.getHistoryURLFromBlobURL(this.activeFile.url);
}
},
},
methods: {
<<<<<<< HEAD
rawPreviewToggle() {
Helper.setCurrentFileRawOrPreview();
}
=======
setRawPreviewMode() {
},
>>>>>>> 51a936fb3d2cdbd133a3b0eed463b47c1c92fe7d
},
});
}
......
let RepoFileOptions = {
const RepoFileOptions = {
template: `
<tr v-if='isMini' class='repo-file-options'>
<td>
......@@ -21,8 +21,8 @@ let RepoFileOptions = {
props: {
name: 'repo-file-options',
isMini: Boolean,
projectName: String
}
}
projectName: String,
},
};
export default RepoFileOptions;
import Service from './repo_service'
import Store from './repo_store'
import Service from './repo_service';
import Store from './repo_store';
import Flash from '../flash';
let RepoHelper = {
const RepoHelper = {
isTree(data) {
return data.hasOwnProperty('blobs');
return Object.hasOwnProperty.call(data, 'blobs');
},
monacoInstance: undefined,
......@@ -14,49 +15,42 @@ let RepoHelper = {
: Date,
getLanguagesForMimeType(mimetypeNeedle) {
const langs = monaco.languages.getLanguages();
let lang = '';
langs.every((lang) => {
const hasLang = lang.mimetypes.some((mimetype) => {
return mimetypeNeedle === mimetype
});
if(hasLang) {
lang = lang.id;
return true;
}
return false;
const langs = window.monaco.languages.getLanguages();
langs.map((lang) => {
const hasLang = lang.mimetypes.some(mimetype => mimetypeNeedle === mimetype);
if (hasLang) return lang.id;
return lang;
});
},
blobURLtoParent(url) {
let joined = '';
const split = url.split('/');
split.pop();
const blobIndex = split.indexOf('blob');
if(blobIndex > -1) {
if (blobIndex > -1) {
split[blobIndex] = 'tree';
}
joined = split.join('/');
return split.join('/');
},
insertNewFilesIntoParentDir(inDirectory, oldList, newList) {
let indexOfFile;
if(!inDirectory) {
if (!inDirectory) {
return newList;
}
oldList.find((file, i) => {
if(file.url === inDirectory.url){
indexOfFile = i+1;
if (file.url === inDirectory.url) {
indexOfFile = i + 1;
return true;
}
return false;
});
if(indexOfFile){
if (indexOfFile) {
// insert new list into old list
newList.forEach((newFile) => {
newFile.level = inDirectory.level + 1;
oldList.splice(indexOfFile, 0, newFile);
const file = newFile;
file.level = inDirectory.level + 1;
oldList.splice(indexOfFile, 0, file);
});
return oldList;
}
......@@ -64,10 +58,9 @@ let RepoHelper = {
},
resetBinaryTypes() {
let s = '';
for(s in Store.binaryTypes) {
Store.binaryTypes[s] = false;
}
Object.keys(Store.binaryTypes).forEach((typeKey) => {
Store.binaryTypes[typeKey] = false;
});
},
setCurrentFileRawOrPreview() {
......@@ -76,6 +69,7 @@ let RepoHelper = {
},
setActiveFile(file) {
<<<<<<< HEAD
// don't load the file that is already loaded
if(file.url === Store.activeFile.url) return;
......@@ -84,9 +78,17 @@ let RepoHelper = {
if(openedFile.active) {
Store.activeFile = openedFile;
Store.activeFileIndex = i;
=======
Store.openedFiles = Store.openedFiles.map((openedFile) => {
const activeFile = openedFile;
activeFile.active = file.url === activeFile.url; // eslint-disable-line no-param-reassign
if (activeFile.active) {
Store.activeFile = activeFile;
>>>>>>> 51a936fb3d2cdbd133a3b0eed463b47c1c92fe7d
}
return openedFile;
return activeFile;
});
<<<<<<< HEAD
// reset the active file raw
Store.activeFile.raw = false;
......@@ -94,39 +96,48 @@ let RepoHelper = {
Store.activeFileLabel = 'Raw';
if(file.binary) {
=======
if (file.binary) {
>>>>>>> 51a936fb3d2cdbd133a3b0eed463b47c1c92fe7d
Store.blobRaw = file.base64;
} else {
Store.blobRaw = file.plain;
}
if(!file.loading){
if (!file.loading) {
this.toURL(file.url);
}
Store.binary = file.binary;
},
removeFromOpenedFiles(file) {
if(file.type === 'tree') return;
Store.openedFiles = Store.openedFiles.filter((openedFile) => {
return openedFile.url !== file.url;
});
if (file.type === 'tree') return;
Store.openedFiles = Store.openedFiles.filter(openedFile => openedFile.url !== file.url);
},
addToOpenedFiles(file) {
<<<<<<< HEAD
const openedFilesAlreadyExists = Store.openedFiles.some((openedFile) => {
return openedFile.url === file.url
});
if(!openedFilesAlreadyExists) {
file.changed = false;
=======
const openedFilesAlreadyExists = Store.openedFiles
.some(openedFile => openedFile.url === file.url);
if (!openedFilesAlreadyExists) {
>>>>>>> 51a936fb3d2cdbd133a3b0eed463b47c1c92fe7d
Store.openedFiles.push(file);
}
},
/* eslint-disable no-param-reassign */
setDirectoryOpen(tree) {
if(tree) {
if (tree) {
tree.opened = true;
tree.icon = 'fa-folder-open';
}
},
/* eslint-enable no-param-reassign */
getRawURLFromBlobURL(url) {
return url.replace('blob', 'raw');
......@@ -144,8 +155,9 @@ let RepoHelper = {
Service.getBase64Content(url)
.then((response) => {
Store.blobRaw = response;
file.base64 = response
});
file.base64 = response; // eslint-disable-line no-param-reassign
})
.catch(this.loadingError);
},
setActiveFileContents(contents) {
......@@ -156,59 +168,80 @@ let RepoHelper = {
},
toggleFakeTab(loading, file) {
if(loading) {
if (loading) {
const randomURL = this.Time.now();
const newFakeFile = {
active: false,
binary: true,
type: 'blob',
loading: true,
mime_type:'loading',
mime_type: 'loading',
name: 'loading',
url: randomURL
url: randomURL,
};
Store.openedFiles.push(newFakeFile);
return newFakeFile;
} else {
}
this.removeFromOpenedFiles(file);
return null;
}
},
setLoading(loading, file) {
if(Service.url.indexOf('tree') > -1) {
if (Service.url.indexOf('tree') > -1) {
Store.loading.tree = loading;
} else if(Service.url.indexOf('blob') > -1) {
} else if (Service.url.indexOf('blob') > -1) {
Store.loading.blob = loading;
return this.toggleFakeTab(loading, file);
}
return undefined;
},
// may be tree or file.
<<<<<<< HEAD
getContent(file) {
// don't load the same active file. That's silly.
// if(file && file.url === this.activeFile.url) return;
=======
getContent(treeOrFile) {
let file = treeOrFile;
>>>>>>> 51a936fb3d2cdbd133a3b0eed463b47c1c92fe7d
const loadingData = this.setLoading(true);
Service.getContent()
.then((response) => {
let data = response.data;
const data = response.data;
this.setLoading(false, loadingData);
Store.isTree = this.isTree(data);
if(!Store.isTree) {
if(!file) {
if (!Store.isTree) {
if (!file) {
file = data;
}
// it's a blob
Store.binary = data.binary;
if(data.binary) {
if (data.binary) {
Store.binaryMimeType = data.mime_type;
this.setBinaryDataAsBase64(
this.getRawURLFromBlobURL(file.url),
data
data,
);
data.binary = true;
<<<<<<< HEAD
} else {
Store.blobRaw = data.plain;
=======
if (!file.url) {
file.url = location.pathname;
}
data.url = file.url;
this.addToOpenedFiles(data);
this.setActiveFile(data);
} else {
Store.blobRaw = data.plain;
if (!file.url) {
file.url = location.pathname;
}
data.url = file.url;
>>>>>>> 51a936fb3d2cdbd133a3b0eed463b47c1c92fe7d
data.binary = false;
}
if(!file.url) {
......@@ -220,7 +253,7 @@ let RepoHelper = {
this.setActiveFile(data);
// if the file tree is empty
if(Store.files.length === 0) {
if (Store.files.length === 0) {
const parentURL = this.blobURLtoParent(Service.url);
Service.url = parentURL;
this.getContent();
......@@ -228,14 +261,14 @@ let RepoHelper = {
} else {
// it's a tree
this.setDirectoryOpen(file);
let newDirectory = this.dataToListOfFiles(data);
const newDirectory = this.dataToListOfFiles(data);
Store.files = this.insertNewFilesIntoParentDir(file, Store.files, newDirectory);
Store.prevURL = this.blobURLtoParent(Service.url);
}
})
.catch((response)=> {
.catch(() => {
this.setLoading(false, loadingData);
new Flash('Unable to load the file at this time.')
this.loadingError();
});
},
......@@ -243,23 +276,23 @@ let RepoHelper = {
return `fa-${icon}`;
},
/* eslint-disable no-param-reassign */
removeChildFilesOfTree(tree) {
let foundTree = false;
Store.files = Store.files.filter((file) => {
if(file.url === tree.url) {
if (file.url === tree.url) {
foundTree = true;
}
if(foundTree) {
return file.level <= tree.level
} else {
return true;
if (foundTree) {
return file.level <= tree.level;
}
return true;
});
tree.opened = false;
tree.icon = 'fa-folder';
},
/* eslint-enable no-param-reassign */
blobToSimpleBlob(blob) {
return {
......@@ -269,8 +302,8 @@ let RepoHelper = {
icon: this.toFA(blob.icon),
lastCommitMessage: blob.last_commit.message,
lastCommitUpdate: blob.last_commit.committed_date,
level: 0
}
level: 0,
};
},
treeToSimpleTree(tree) {
......@@ -279,16 +312,16 @@ let RepoHelper = {
name: tree.name,
url: tree.url,
icon: this.toFA(tree.icon),
level: 0
}
level: 0,
};
},
dataToListOfFiles(data) {
let a = [];
const a = [];
//push in blobs
// push in blobs
data.blobs.forEach((blob) => {
a.push(this.blobToSimpleBlob(blob))
a.push(this.blobToSimpleBlob(blob));
});
data.trees.forEach((tree) => {
......@@ -301,32 +334,36 @@ let RepoHelper = {
name: submodule.name,
url: submodule.url,
icon: this.toFA(submodule.icon),
level: 0
})
level: 0,
});
});
return a;
},
genKey () {
return this.Time.now().toFixed(3)
genKey() {
return this.Time.now().toFixed(3);
},
_key: '',
key: '',
getStateKey () {
return this._key
getStateKey() {
return this.key;
},
setStateKey (key) {
this._key = key;
setStateKey(key) {
this.key = key;
},
toURL(url) {
var history = window.history;
this._key = this.genKey();
history.pushState({ key: this._key }, '', url);
}
const history = window.history;
this.key = this.genKey();
history.pushState({ key: this.key }, '', url);
},
loadingError() {
new Flash('Unable to load the file at this time.'); // eslint-disable-line no-new
},
};
export default RepoHelper;
let RepoLoadingFile = {
const RepoLoadingFile = {
template: `
<tr v-if='loading.tree && !hasFiles'>
<td>
......@@ -22,13 +22,13 @@ let RepoLoadingFile = {
methods: {
lineOfCode(n) {
return `line-of-code-${n}`;
}
},
},
props: {
loading: Object,
hasFiles: Boolean,
isMini: Boolean
}
isMini: Boolean,
},
};
export default RepoLoadingFile;
import Store from './repo_store'
import Store from './repo_store';
let RepoMiniMixin = {
const RepoMiniMixin = {
computed: {
isMini() {
return !!Store.openedFiles.length;
}
},
},
};
......
let RepoPreviousDirectory = {
const RepoPreviousDirectory = {
template: `
<tr>
<td colspan='3'>
......@@ -8,13 +8,13 @@ let RepoPreviousDirectory = {
`,
props: {
name: 'repo-previous-directory',
prevUrl: String
prevUrl: String,
},
methods: {
linkClicked(file) {
this.$emit('linkclicked', file);
}
}
},
},
};
export default RepoPreviousDirectory;
import axios from 'axios';
let RepoService = {
const RepoService = {
url: '',
params: {
params: {
format: 'json'
}
format: 'json',
},
},
setUrl(url) {
......@@ -14,16 +14,16 @@ let RepoService = {
paramsWithRich(url) {
// copy the obj so we don't modify perm.
let params = JSON.parse(JSON.stringify(this.params));
if(url.substr(url.length-2) === 'md') {
const params = JSON.parse(JSON.stringify(this.params));
if (url.substr(url.length - 2) === 'md') {
params.params.viewer = 'rich';
}
return params;
},
getContent(url) {
if(url){
return axios.get(url, this.paramsWithRich(url, params));
if (url) {
return axios.get(url, this.paramsWithRich(url, this.params));
}
return axios.get(this.url, this.paramsWithRich(this.url, this.params));
},
......@@ -31,10 +31,10 @@ let RepoService = {
getBase64Content(url) {
return axios
.get(url, {
responseType: 'arraybuffer'
responseType: 'arraybuffer',
})
.then(response => new Buffer(response.data, 'binary').toString('base64'))
}
.then(response => new Buffer(response.data, 'binary').toString('base64'));
},
};
export default RepoService;
import Service from './repo_service'
import Helper from './repo_helper'
import Vue from 'vue'
import Store from './repo_store'
import RepoPreviousDirectory from './repo_prev_directory'
import RepoFileOptions from './repo_file_options'
import RepoFile from './repo_file'
import RepoLoadingFile from './repo_loading_file'
import RepoMiniMixin from './repo_mini_mixin'
import Vue from 'vue';
import Service from './repo_service';
import Helper from './repo_helper';
import Store from './repo_store';
import RepoPreviousDirectory from './repo_prev_directory';
import RepoFileOptions from './repo_file_options';
import RepoFile from './repo_file';
import RepoLoadingFile from './repo_loading_file';
import RepoMiniMixin from './repo_mini_mixin';
export default class RepoSidebar {
constructor(url) {
......@@ -35,31 +35,37 @@ export default class RepoSidebar {
methods: {
addPopEventListener() {
window.addEventListener('popstate', () => {
if(location.href.indexOf('#') > -1) return;
if (location.href.indexOf('#') > -1) return;
this.linkClicked({
url: location.href
url: location.href,
});
});
},
linkClicked(file) {
let url = '';
if(typeof file === 'object') {
if(file.type === 'tree' && file.opened) {
if (typeof file === 'object') {
if (file.type === 'tree' && file.opened) {
Helper.removeChildFilesOfTree(file);
<<<<<<< HEAD
return;
} else {
url = file.url;
Service.url = url;
Helper.getContent(file);
=======
>>>>>>> 51a936fb3d2cdbd133a3b0eed463b47c1c92fe7d
}
} else if(typeof file === 'string') {
url = file.url;
Service.url = url;
Helper.getContent(file);
} else if (typeof file === 'string') {
// go back
url = file;
Service.url = url;
Helper.getContent();
}
}
},
},
});
}
......
let RepoStore = {
const RepoStore = {
service: '',
editor: '',
sidebar: '',
......@@ -22,9 +22,12 @@ let RepoStore = {
plain: '',
size: 0,
url: '',
<<<<<<< HEAD
raw: false,
newContent: '',
changed: false
=======
>>>>>>> 51a936fb3d2cdbd133a3b0eed463b47c1c92fe7d
},
activeFileIndex: 0,
activeLine: 0,
......@@ -32,15 +35,15 @@ let RepoStore = {
files: [],
binary: false,
binaryMimeType: '',
//scroll bar space for windows
// scroll bar space for windows
scrollWidth: 0,
binaryTypes: {
png: false,
markdown: false
markdown: false,
},
loading: {
tree: false,
blob: false
}
blob: false,
},
};
export default RepoStore;
import RepoHelper from './repo_helper'
import RepoHelper from './repo_helper';
let RepoTab = {
const RepoTab = {
template: `
<li>
<a href='#' @click.prevent='xClicked(tab)' v-if='!tab.loading'>
......@@ -34,7 +34,7 @@ let RepoTab = {
xClicked(file) {
if(file.changed) return;
RepoHelper.removeFromOpenedFiles(file);
}
}
},
},
};
export default RepoTab;
import Vue from 'vue';
import Store from './repo_store'
import RepoTab from './repo_tab'
import RepoMiniMixin from './repo_mini_mixin'
import Store from './repo_store';
import RepoTab from './repo_tab';
import RepoMiniMixin from './repo_mini_mixin';
export default class RepoTabs {
constructor() {
this.styleTabsForWindows();
RepoTabs.styleTabsForWindows();
this.initVue();
}
......@@ -20,7 +20,7 @@ export default class RepoTabs {
});
}
styleTabsForWindows() {
static styleTabsForWindows() {
const scrollWidth = Number(document.body.dataset.scrollWidth);
Store.scrollWidth = scrollWidth;
}
......
import Vue from 'vue';
import Store from './repo_store';
export default class RepoViewToggler {
constructor() {
this.initVue();
}
initVue() {
this.vue = new Vue({
el: '#view-toggler',
data: () => Store,
});
}
}
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