Merge branch '64190-add-mr-form' into 'master'
Resolve "Support runtime Visual Review configuration" See merge request gitlab-org/gitlab-ce!30481
This commit is contained in:
commit
6686cd3503
|
@ -1,148 +1,39 @@
|
||||||
import { BLACK, COMMENT_BOX, MUTED, LOGOUT } from './constants';
|
import { nextView } from '../store';
|
||||||
import { clearNote, postError } from './note';
|
import { localStorage, COMMENT_BOX, LOGOUT } from '../shared';
|
||||||
import {
|
import { clearNote } from './note';
|
||||||
buttonClearStyles,
|
import { buttonClearStyles } from './utils';
|
||||||
selectCommentBox,
|
import { addForm } from './wrapper';
|
||||||
selectCommentButton,
|
import { changeSelectedMr, selectedMrNote } from './comment_mr_note';
|
||||||
selectNote,
|
import postComment from './comment_post';
|
||||||
selectNoteContainer,
|
import { saveComment, getSavedComment } from './comment_storage';
|
||||||
} from './utils';
|
|
||||||
|
|
||||||
const comment = `
|
const comment = state => {
|
||||||
|
const savedComment = getSavedComment();
|
||||||
|
|
||||||
|
return `
|
||||||
<div>
|
<div>
|
||||||
<textarea id="${COMMENT_BOX}" name="${COMMENT_BOX}" rows="3" placeholder="Enter your feedback or idea" class="gitlab-input" aria-required="true"></textarea>
|
<textarea id="${COMMENT_BOX}" name="${COMMENT_BOX}" rows="3" placeholder="Enter your feedback or idea" class="gitlab-input" aria-required="true">${savedComment}</textarea>
|
||||||
|
${selectedMrNote(state)}
|
||||||
<p class="gitlab-metadata-note">Additional metadata will be included: browser, OS, current page, user agent, and viewport dimensions.</p>
|
<p class="gitlab-metadata-note">Additional metadata will be included: browser, OS, current page, user agent, and viewport dimensions.</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="gitlab-button-wrapper">
|
<div class="gitlab-button-wrapper">
|
||||||
<button class="gitlab-button gitlab-button-secondary" style="${buttonClearStyles}" type="button" id="${LOGOUT}"> Log out </button>
|
|
||||||
<button class="gitlab-button gitlab-button-success" style="${buttonClearStyles}" type="button" id="gitlab-comment-button"> Send feedback </button>
|
<button class="gitlab-button gitlab-button-success" style="${buttonClearStyles}" type="button" id="gitlab-comment-button"> Send feedback </button>
|
||||||
|
<button class="gitlab-button gitlab-button-secondary" style="${buttonClearStyles}" type="button" id="${LOGOUT}"> Log out </button>
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const resetCommentButton = () => {
|
|
||||||
const commentButton = selectCommentButton();
|
|
||||||
|
|
||||||
/* eslint-disable-next-line @gitlab/i18n/no-non-i18n-strings */
|
|
||||||
commentButton.innerText = 'Send feedback';
|
|
||||||
commentButton.classList.replace('gitlab-button-secondary', 'gitlab-button-success');
|
|
||||||
commentButton.style.opacity = 1;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const resetCommentBox = () => {
|
// This function is here becaause it is called only from the comment view
|
||||||
const commentBox = selectCommentBox();
|
// If we reach a design where we can logout from multiple views, promote this
|
||||||
commentBox.style.pointerEvents = 'auto';
|
// to it's own package
|
||||||
commentBox.style.color = BLACK;
|
const logoutUser = state => {
|
||||||
|
localStorage.removeItem('token');
|
||||||
|
localStorage.removeItem('mergeRequestId');
|
||||||
|
state.token = '';
|
||||||
|
state.mergeRequestId = '';
|
||||||
|
|
||||||
|
clearNote();
|
||||||
|
addForm(nextView(state, COMMENT_BOX));
|
||||||
};
|
};
|
||||||
|
|
||||||
const resetCommentText = () => {
|
export { changeSelectedMr, comment, logoutUser, postComment, saveComment };
|
||||||
const commentBox = selectCommentBox();
|
|
||||||
commentBox.value = '';
|
|
||||||
};
|
|
||||||
|
|
||||||
const resetComment = () => {
|
|
||||||
resetCommentButton();
|
|
||||||
resetCommentBox();
|
|
||||||
resetCommentText();
|
|
||||||
};
|
|
||||||
|
|
||||||
const confirmAndClear = feedbackInfo => {
|
|
||||||
const commentButton = selectCommentButton();
|
|
||||||
const currentNote = selectNote();
|
|
||||||
const noteContainer = selectNoteContainer();
|
|
||||||
|
|
||||||
/* eslint-disable-next-line @gitlab/i18n/no-non-i18n-strings */
|
|
||||||
commentButton.innerText = 'Feedback sent';
|
|
||||||
noteContainer.style.visibility = 'visible';
|
|
||||||
currentNote.insertAdjacentHTML('beforeend', feedbackInfo);
|
|
||||||
|
|
||||||
setTimeout(resetComment, 1000);
|
|
||||||
setTimeout(clearNote, 6000);
|
|
||||||
};
|
|
||||||
|
|
||||||
const setInProgressState = () => {
|
|
||||||
const commentButton = selectCommentButton();
|
|
||||||
const commentBox = selectCommentBox();
|
|
||||||
|
|
||||||
/* eslint-disable-next-line @gitlab/i18n/no-non-i18n-strings */
|
|
||||||
commentButton.innerText = 'Sending feedback';
|
|
||||||
commentButton.classList.replace('gitlab-button-success', 'gitlab-button-secondary');
|
|
||||||
commentButton.style.opacity = 0.5;
|
|
||||||
commentBox.style.color = MUTED;
|
|
||||||
commentBox.style.pointerEvents = 'none';
|
|
||||||
};
|
|
||||||
|
|
||||||
const postComment = ({
|
|
||||||
href,
|
|
||||||
platform,
|
|
||||||
browser,
|
|
||||||
userAgent,
|
|
||||||
innerWidth,
|
|
||||||
innerHeight,
|
|
||||||
projectId,
|
|
||||||
projectPath,
|
|
||||||
mergeRequestId,
|
|
||||||
mrUrl,
|
|
||||||
token,
|
|
||||||
}) => {
|
|
||||||
// Clear any old errors
|
|
||||||
clearNote(COMMENT_BOX);
|
|
||||||
|
|
||||||
setInProgressState();
|
|
||||||
|
|
||||||
const commentText = selectCommentBox().value.trim();
|
|
||||||
|
|
||||||
if (!commentText) {
|
|
||||||
/* eslint-disable-next-line @gitlab/i18n/no-non-i18n-strings */
|
|
||||||
postError('Your comment appears to be empty.', COMMENT_BOX);
|
|
||||||
resetCommentBox();
|
|
||||||
resetCommentButton();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const detailText = `
|
|
||||||
\n
|
|
||||||
<details>
|
|
||||||
<summary>Metadata</summary>
|
|
||||||
Posted from ${href} | ${platform} | ${browser} | ${innerWidth} x ${innerHeight}.
|
|
||||||
<br /><br />
|
|
||||||
<em>User agent: ${userAgent}</em>
|
|
||||||
</details>
|
|
||||||
`;
|
|
||||||
|
|
||||||
const url = `
|
|
||||||
${mrUrl}/api/v4/projects/${projectId}/merge_requests/${mergeRequestId}/discussions`;
|
|
||||||
|
|
||||||
const body = `${commentText} ${detailText}`;
|
|
||||||
|
|
||||||
fetch(url, {
|
|
||||||
method: 'POST',
|
|
||||||
headers: {
|
|
||||||
'PRIVATE-TOKEN': token,
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
},
|
|
||||||
body: JSON.stringify({ body }),
|
|
||||||
})
|
|
||||||
.then(response => {
|
|
||||||
if (response.ok) {
|
|
||||||
return response.json();
|
|
||||||
}
|
|
||||||
|
|
||||||
throw new Error(`${response.status}: ${response.statusText}`);
|
|
||||||
})
|
|
||||||
.then(data => {
|
|
||||||
const commentId = data.notes[0].id;
|
|
||||||
const feedbackLink = `${mrUrl}/${projectPath}/merge_requests/${mergeRequestId}#note_${commentId}`;
|
|
||||||
const feedbackInfo = `Feedback sent. View at <a class="gitlab-link" href="${feedbackLink}">${projectPath} #${mergeRequestId} (comment ${commentId})</a>`;
|
|
||||||
confirmAndClear(feedbackInfo);
|
|
||||||
})
|
|
||||||
.catch(err => {
|
|
||||||
postError(
|
|
||||||
`Your comment could not be sent. Please try again. Error: ${err.message}`,
|
|
||||||
COMMENT_BOX,
|
|
||||||
);
|
|
||||||
resetCommentBox();
|
|
||||||
resetCommentButton();
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
export { comment, postComment };
|
|
||||||
|
|
|
@ -0,0 +1,31 @@
|
||||||
|
import { nextView } from '../store';
|
||||||
|
import { localStorage, CHANGE_MR_ID_BUTTON, COMMENT_BOX } from '../shared';
|
||||||
|
import { clearNote } from './note';
|
||||||
|
import { buttonClearStyles } from './utils';
|
||||||
|
import { addForm } from './wrapper';
|
||||||
|
|
||||||
|
const selectedMrNote = state => {
|
||||||
|
const { mrUrl, projectPath, mergeRequestId } = state;
|
||||||
|
|
||||||
|
const mrLink = `${mrUrl}/${projectPath}/merge_requests/${mergeRequestId}`;
|
||||||
|
|
||||||
|
return `
|
||||||
|
<p class="gitlab-metadata-note">
|
||||||
|
This posts to merge request <a class="gitlab-link" href="${mrLink}">!${mergeRequestId}</a>.
|
||||||
|
<button style="${buttonClearStyles}" type="button" id="${CHANGE_MR_ID_BUTTON}" class="gitlab-link gitlab-link-button">Change</button>
|
||||||
|
</p>
|
||||||
|
`;
|
||||||
|
};
|
||||||
|
|
||||||
|
const clearMrId = state => {
|
||||||
|
localStorage.removeItem('mergeRequestId');
|
||||||
|
state.mergeRequestId = '';
|
||||||
|
};
|
||||||
|
|
||||||
|
const changeSelectedMr = state => {
|
||||||
|
clearMrId(state);
|
||||||
|
clearNote();
|
||||||
|
addForm(nextView(state, COMMENT_BOX));
|
||||||
|
};
|
||||||
|
|
||||||
|
export { changeSelectedMr, selectedMrNote };
|
|
@ -0,0 +1,145 @@
|
||||||
|
import { BLACK, COMMENT_BOX, MUTED } from '../shared';
|
||||||
|
import { clearSavedComment } from './comment_storage';
|
||||||
|
import { clearNote, postError } from './note';
|
||||||
|
import { selectCommentBox, selectCommentButton, selectNote, selectNoteContainer } from './utils';
|
||||||
|
|
||||||
|
const resetCommentButton = () => {
|
||||||
|
const commentButton = selectCommentButton();
|
||||||
|
|
||||||
|
/* eslint-disable-next-line @gitlab/i18n/no-non-i18n-strings */
|
||||||
|
commentButton.innerText = 'Send feedback';
|
||||||
|
commentButton.classList.replace('gitlab-button-secondary', 'gitlab-button-success');
|
||||||
|
commentButton.style.opacity = 1;
|
||||||
|
};
|
||||||
|
|
||||||
|
const resetCommentBox = () => {
|
||||||
|
const commentBox = selectCommentBox();
|
||||||
|
commentBox.style.pointerEvents = 'auto';
|
||||||
|
commentBox.style.color = BLACK;
|
||||||
|
};
|
||||||
|
|
||||||
|
const resetCommentText = () => {
|
||||||
|
const commentBox = selectCommentBox();
|
||||||
|
commentBox.value = '';
|
||||||
|
clearSavedComment();
|
||||||
|
};
|
||||||
|
|
||||||
|
const resetComment = () => {
|
||||||
|
resetCommentButton();
|
||||||
|
resetCommentBox();
|
||||||
|
resetCommentText();
|
||||||
|
};
|
||||||
|
|
||||||
|
const confirmAndClear = feedbackInfo => {
|
||||||
|
const commentButton = selectCommentButton();
|
||||||
|
const currentNote = selectNote();
|
||||||
|
const noteContainer = selectNoteContainer();
|
||||||
|
|
||||||
|
/* eslint-disable-next-line @gitlab/i18n/no-non-i18n-strings */
|
||||||
|
commentButton.innerText = 'Feedback sent';
|
||||||
|
noteContainer.style.visibility = 'visible';
|
||||||
|
currentNote.insertAdjacentHTML('beforeend', feedbackInfo);
|
||||||
|
|
||||||
|
setTimeout(resetComment, 1000);
|
||||||
|
setTimeout(clearNote, 6000);
|
||||||
|
};
|
||||||
|
|
||||||
|
const setInProgressState = () => {
|
||||||
|
const commentButton = selectCommentButton();
|
||||||
|
const commentBox = selectCommentBox();
|
||||||
|
|
||||||
|
/* eslint-disable-next-line @gitlab/i18n/no-non-i18n-strings */
|
||||||
|
commentButton.innerText = 'Sending feedback';
|
||||||
|
commentButton.classList.replace('gitlab-button-success', 'gitlab-button-secondary');
|
||||||
|
commentButton.style.opacity = 0.5;
|
||||||
|
commentBox.style.color = MUTED;
|
||||||
|
commentBox.style.pointerEvents = 'none';
|
||||||
|
};
|
||||||
|
|
||||||
|
const commentErrors = error => {
|
||||||
|
switch (error.status) {
|
||||||
|
case 401:
|
||||||
|
/* eslint-disable-next-line @gitlab/i18n/no-non-i18n-strings */
|
||||||
|
return 'Unauthorized. You may have entered an incorrect authentication token.';
|
||||||
|
case 404:
|
||||||
|
/* eslint-disable-next-line @gitlab/i18n/no-non-i18n-strings */
|
||||||
|
return 'Not found. You may have entered an incorrect merge request ID.';
|
||||||
|
default:
|
||||||
|
/* eslint-disable-next-line @gitlab/i18n/no-non-i18n-strings */
|
||||||
|
return `Your comment could not be sent. Please try again. Error: ${error.message}`;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const postComment = ({
|
||||||
|
platform,
|
||||||
|
browser,
|
||||||
|
userAgent,
|
||||||
|
innerWidth,
|
||||||
|
innerHeight,
|
||||||
|
projectId,
|
||||||
|
projectPath,
|
||||||
|
mergeRequestId,
|
||||||
|
mrUrl,
|
||||||
|
token,
|
||||||
|
}) => {
|
||||||
|
// Clear any old errors
|
||||||
|
clearNote(COMMENT_BOX);
|
||||||
|
|
||||||
|
setInProgressState();
|
||||||
|
|
||||||
|
const commentText = selectCommentBox().value.trim();
|
||||||
|
// Get the href at the last moment to support SPAs
|
||||||
|
const { href } = window.location;
|
||||||
|
|
||||||
|
if (!commentText) {
|
||||||
|
/* eslint-disable-next-line @gitlab/i18n/no-non-i18n-strings */
|
||||||
|
postError('Your comment appears to be empty.', COMMENT_BOX);
|
||||||
|
resetCommentBox();
|
||||||
|
resetCommentButton();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const detailText = `
|
||||||
|
\n
|
||||||
|
<details>
|
||||||
|
<summary>Metadata</summary>
|
||||||
|
Posted from ${href} | ${platform} | ${browser} | ${innerWidth} x ${innerHeight}.
|
||||||
|
<br /><br />
|
||||||
|
<em>User agent: ${userAgent}</em>
|
||||||
|
</details>
|
||||||
|
`;
|
||||||
|
|
||||||
|
const url = `
|
||||||
|
${mrUrl}/api/v4/projects/${projectId}/merge_requests/${mergeRequestId}/discussions`;
|
||||||
|
|
||||||
|
const body = `${commentText} ${detailText}`;
|
||||||
|
|
||||||
|
fetch(url, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'PRIVATE-TOKEN': token,
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
body: JSON.stringify({ body }),
|
||||||
|
})
|
||||||
|
.then(response => {
|
||||||
|
if (response.ok) {
|
||||||
|
return response.json();
|
||||||
|
}
|
||||||
|
|
||||||
|
throw response;
|
||||||
|
})
|
||||||
|
.then(data => {
|
||||||
|
const commentId = data.notes[0].id;
|
||||||
|
const feedbackLink = `${mrUrl}/${projectPath}/merge_requests/${mergeRequestId}#note_${commentId}`;
|
||||||
|
const feedbackInfo = `Feedback sent. View at <a class="gitlab-link" href="${feedbackLink}">${projectPath} !${mergeRequestId} (comment ${commentId})</a>`;
|
||||||
|
confirmAndClear(feedbackInfo);
|
||||||
|
})
|
||||||
|
.catch(err => {
|
||||||
|
postError(commentErrors(err), COMMENT_BOX);
|
||||||
|
resetCommentBox();
|
||||||
|
resetCommentButton();
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export default postComment;
|
|
@ -0,0 +1,20 @@
|
||||||
|
import { selectCommentBox } from './utils';
|
||||||
|
import { sessionStorage } from '../shared';
|
||||||
|
|
||||||
|
const getSavedComment = () => sessionStorage.getItem('comment') || '';
|
||||||
|
|
||||||
|
const saveComment = () => {
|
||||||
|
const currentComment = selectCommentBox();
|
||||||
|
|
||||||
|
// This may be added to any view via top-level beforeunload listener
|
||||||
|
// so let's skip if it does not apply
|
||||||
|
if (currentComment && currentComment.value) {
|
||||||
|
sessionStorage.setItem('comment', currentComment.value);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const clearSavedComment = () => {
|
||||||
|
sessionStorage.removeItem('comment');
|
||||||
|
};
|
||||||
|
|
||||||
|
export { getSavedComment, saveComment, clearSavedComment };
|
|
@ -0,0 +1,17 @@
|
||||||
|
import { REMEMBER_ITEM } from '../shared';
|
||||||
|
import { buttonClearStyles } from './utils';
|
||||||
|
|
||||||
|
/* eslint-disable-next-line @gitlab/i18n/no-non-i18n-strings */
|
||||||
|
const rememberBox = (rememberText = 'Remember me') => `
|
||||||
|
<div class="gitlab-checkbox-wrapper">
|
||||||
|
<input type="checkbox" id="${REMEMBER_ITEM}" name="${REMEMBER_ITEM}" value="remember">
|
||||||
|
<label for="${REMEMBER_ITEM}" class="gitlab-checkbox-label">${rememberText}</label>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
const submitButton = buttonId => `
|
||||||
|
<div class="gitlab-button-wrapper">
|
||||||
|
<button class="gitlab-button-wide gitlab-button gitlab-button-success" style="${buttonClearStyles}" type="button" id="${buttonId}"> Submit </button>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
export { rememberBox, submitButton };
|
|
@ -1,33 +1,23 @@
|
||||||
import { comment, postComment } from './comment';
|
import { changeSelectedMr, comment, logoutUser, postComment, saveComment } from './comment';
|
||||||
import {
|
|
||||||
COLLAPSE_BUTTON,
|
|
||||||
COMMENT_BUTTON,
|
|
||||||
FORM_CONTAINER,
|
|
||||||
LOGIN,
|
|
||||||
LOGOUT,
|
|
||||||
REVIEW_CONTAINER,
|
|
||||||
} from './constants';
|
|
||||||
import { authorizeUser, login } from './login';
|
import { authorizeUser, login } from './login';
|
||||||
|
import { addMr, mrForm } from './mr_id';
|
||||||
import { note } from './note';
|
import { note } from './note';
|
||||||
import { selectContainer } from './utils';
|
import { selectContainer, selectForm } from './utils';
|
||||||
import { buttonAndForm, logoutUser, toggleForm } from './wrapper';
|
import { buttonAndForm, toggleForm } from './wrapper';
|
||||||
import { collapseButton } from './wrapper_icons';
|
|
||||||
|
|
||||||
export {
|
export {
|
||||||
|
addMr,
|
||||||
authorizeUser,
|
authorizeUser,
|
||||||
buttonAndForm,
|
buttonAndForm,
|
||||||
collapseButton,
|
changeSelectedMr,
|
||||||
comment,
|
comment,
|
||||||
login,
|
login,
|
||||||
logoutUser,
|
logoutUser,
|
||||||
|
mrForm,
|
||||||
note,
|
note,
|
||||||
postComment,
|
postComment,
|
||||||
|
saveComment,
|
||||||
selectContainer,
|
selectContainer,
|
||||||
|
selectForm,
|
||||||
toggleForm,
|
toggleForm,
|
||||||
COLLAPSE_BUTTON,
|
|
||||||
COMMENT_BUTTON,
|
|
||||||
FORM_CONTAINER,
|
|
||||||
LOGIN,
|
|
||||||
LOGOUT,
|
|
||||||
REVIEW_CONTAINER,
|
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,35 +1,31 @@
|
||||||
import { LOGIN, REMEMBER_TOKEN, TOKEN_BOX } from './constants';
|
import { nextView } from '../store';
|
||||||
|
import { localStorage, LOGIN, TOKEN_BOX } from '../shared';
|
||||||
import { clearNote, postError } from './note';
|
import { clearNote, postError } from './note';
|
||||||
import { buttonClearStyles, selectRemember, selectToken } from './utils';
|
import { rememberBox, submitButton } from './form_elements';
|
||||||
import { addCommentForm } from './wrapper';
|
import { selectRemember, selectToken } from './utils';
|
||||||
|
import { addForm } from './wrapper';
|
||||||
|
|
||||||
|
const labelText = `
|
||||||
|
Enter your <a class="gitlab-link" href="https://docs.gitlab.com/ee/user/profile/personal_access_tokens.html">personal access token</a>
|
||||||
|
`;
|
||||||
|
|
||||||
const login = `
|
const login = `
|
||||||
<div>
|
<div>
|
||||||
<label for="${TOKEN_BOX}" class="gitlab-label">Enter your <a class="gitlab-link" href="https://docs.gitlab.com/ee/user/profile/personal_access_tokens.html">personal access token</a></label>
|
<label for="${TOKEN_BOX}" class="gitlab-label">${labelText}</label>
|
||||||
<input class="gitlab-input" type="password" id="${TOKEN_BOX}" name="${TOKEN_BOX}" aria-required="true" autocomplete="current-password">
|
<input class="gitlab-input" type="password" id="${TOKEN_BOX}" name="${TOKEN_BOX}" autocomplete="current-password" aria-required="true">
|
||||||
</div>
|
|
||||||
<div class="gitlab-checkbox-wrapper">
|
|
||||||
<input type="checkbox" id="${REMEMBER_TOKEN}" name="${REMEMBER_TOKEN}" value="remember">
|
|
||||||
<label for="${REMEMBER_TOKEN}" class="gitlab-checkbox-label">Remember me</label>
|
|
||||||
</div>
|
|
||||||
<div class="gitlab-button-wrapper">
|
|
||||||
<button class="gitlab-button-wide gitlab-button gitlab-button-success" style="${buttonClearStyles}" type="button" id="${LOGIN}"> Submit </button>
|
|
||||||
</div>
|
</div>
|
||||||
|
${rememberBox()}
|
||||||
|
${submitButton(LOGIN)}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const storeToken = (token, state) => {
|
const storeToken = (token, state) => {
|
||||||
const { localStorage } = window;
|
|
||||||
const rememberMe = selectRemember().checked;
|
const rememberMe = selectRemember().checked;
|
||||||
|
|
||||||
// All the browsers we support have localStorage, so let's silently fail
|
|
||||||
// and go on with the rest of the functionality.
|
|
||||||
try {
|
|
||||||
if (rememberMe) {
|
if (rememberMe) {
|
||||||
localStorage.setItem('token', token);
|
localStorage.setItem('token', token);
|
||||||
}
|
}
|
||||||
} finally {
|
|
||||||
state.token = token;
|
state.token = token;
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const authorizeUser = state => {
|
const authorizeUser = state => {
|
||||||
|
@ -45,7 +41,7 @@ const authorizeUser = state => {
|
||||||
}
|
}
|
||||||
|
|
||||||
storeToken(token, state);
|
storeToken(token, state);
|
||||||
addCommentForm();
|
addForm(nextView(state, LOGIN));
|
||||||
};
|
};
|
||||||
|
|
||||||
export { authorizeUser, login };
|
export { authorizeUser, login, storeToken };
|
||||||
|
|
|
@ -0,0 +1,63 @@
|
||||||
|
import { nextView } from '../store';
|
||||||
|
import { MR_ID, MR_ID_BUTTON, localStorage } from '../shared';
|
||||||
|
import { clearNote, postError } from './note';
|
||||||
|
import { rememberBox, submitButton } from './form_elements';
|
||||||
|
import { selectForm, selectMrBox, selectRemember } from './utils';
|
||||||
|
import { addForm } from './wrapper';
|
||||||
|
|
||||||
|
/* eslint-disable-next-line @gitlab/i18n/no-non-i18n-strings */
|
||||||
|
const mrLabel = `Enter your merge request ID`;
|
||||||
|
/* eslint-disable-next-line @gitlab/i18n/no-non-i18n-strings */
|
||||||
|
const mrRememberText = `Remember this number`;
|
||||||
|
|
||||||
|
const mrForm = `
|
||||||
|
<div>
|
||||||
|
<label for="${MR_ID}" class="gitlab-label">${mrLabel}</label>
|
||||||
|
<input class="gitlab-input" type="text" pattern="[1-9][0-9]*" id="${MR_ID}" name="${MR_ID}" placeholder="e.g., 321" aria-required="true">
|
||||||
|
</div>
|
||||||
|
${rememberBox(mrRememberText)}
|
||||||
|
${submitButton(MR_ID_BUTTON)}
|
||||||
|
`;
|
||||||
|
|
||||||
|
const storeMR = (id, state) => {
|
||||||
|
const rememberMe = selectRemember().checked;
|
||||||
|
|
||||||
|
if (rememberMe) {
|
||||||
|
localStorage.setItem('mergeRequestId', id);
|
||||||
|
}
|
||||||
|
|
||||||
|
state.mergeRequestId = id;
|
||||||
|
};
|
||||||
|
|
||||||
|
const getFormError = (mrNumber, form) => {
|
||||||
|
if (!mrNumber) {
|
||||||
|
/* eslint-disable-next-line @gitlab/i18n/no-non-i18n-strings */
|
||||||
|
return 'Please enter your merge request ID number.';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!form.checkValidity()) {
|
||||||
|
/* eslint-disable-next-line @gitlab/i18n/no-non-i18n-strings */
|
||||||
|
return 'Please remove any non-number values from the field.';
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
|
||||||
|
const addMr = state => {
|
||||||
|
// Clear any old errors
|
||||||
|
clearNote(MR_ID);
|
||||||
|
|
||||||
|
const mrNumber = selectMrBox().value;
|
||||||
|
const form = selectForm();
|
||||||
|
const formError = getFormError(mrNumber, form);
|
||||||
|
|
||||||
|
if (formError) {
|
||||||
|
postError(formError, MR_ID);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
storeMR(mrNumber, state);
|
||||||
|
addForm(nextView(state, MR_ID));
|
||||||
|
};
|
||||||
|
|
||||||
|
export { addMr, mrForm, storeMR };
|
|
@ -1,4 +1,4 @@
|
||||||
import { NOTE, NOTE_CONTAINER, RED } from './constants';
|
import { NOTE, NOTE_CONTAINER, RED } from '../shared';
|
||||||
import { selectById, selectNote, selectNoteContainer } from './utils';
|
import { selectById, selectNote, selectNoteContainer } from './utils';
|
||||||
|
|
||||||
const note = `
|
const note = `
|
||||||
|
|
|
@ -6,12 +6,13 @@ import {
|
||||||
COMMENT_BUTTON,
|
COMMENT_BUTTON,
|
||||||
FORM,
|
FORM,
|
||||||
FORM_CONTAINER,
|
FORM_CONTAINER,
|
||||||
|
MR_ID,
|
||||||
NOTE,
|
NOTE,
|
||||||
NOTE_CONTAINER,
|
NOTE_CONTAINER,
|
||||||
REMEMBER_TOKEN,
|
REMEMBER_ITEM,
|
||||||
REVIEW_CONTAINER,
|
REVIEW_CONTAINER,
|
||||||
TOKEN_BOX,
|
TOKEN_BOX,
|
||||||
} from './constants';
|
} from '../shared';
|
||||||
|
|
||||||
// this style must be applied inline in a handful of components
|
// this style must be applied inline in a handful of components
|
||||||
/* eslint-disable-next-line @gitlab/i18n/no-non-i18n-strings */
|
/* eslint-disable-next-line @gitlab/i18n/no-non-i18n-strings */
|
||||||
|
@ -27,9 +28,10 @@ const selectCommentButton = () => document.getElementById(COMMENT_BUTTON);
|
||||||
const selectContainer = () => document.getElementById(REVIEW_CONTAINER);
|
const selectContainer = () => document.getElementById(REVIEW_CONTAINER);
|
||||||
const selectForm = () => document.getElementById(FORM);
|
const selectForm = () => document.getElementById(FORM);
|
||||||
const selectFormContainer = () => document.getElementById(FORM_CONTAINER);
|
const selectFormContainer = () => document.getElementById(FORM_CONTAINER);
|
||||||
|
const selectMrBox = () => document.getElementById(MR_ID);
|
||||||
const selectNote = () => document.getElementById(NOTE);
|
const selectNote = () => document.getElementById(NOTE);
|
||||||
const selectNoteContainer = () => document.getElementById(NOTE_CONTAINER);
|
const selectNoteContainer = () => document.getElementById(NOTE_CONTAINER);
|
||||||
const selectRemember = () => document.getElementById(REMEMBER_TOKEN);
|
const selectRemember = () => document.getElementById(REMEMBER_ITEM);
|
||||||
const selectToken = () => document.getElementById(TOKEN_BOX);
|
const selectToken = () => document.getElementById(TOKEN_BOX);
|
||||||
|
|
||||||
export {
|
export {
|
||||||
|
@ -41,6 +43,7 @@ export {
|
||||||
selectCommentButton,
|
selectCommentButton,
|
||||||
selectForm,
|
selectForm,
|
||||||
selectFormContainer,
|
selectFormContainer,
|
||||||
|
selectMrBox,
|
||||||
selectNote,
|
selectNote,
|
||||||
selectNoteContainer,
|
selectNoteContainer,
|
||||||
selectRemember,
|
selectRemember,
|
||||||
|
|
|
@ -1,55 +1,32 @@
|
||||||
import { comment } from './comment';
|
import { CLEAR, FORM, FORM_CONTAINER, WHITE } from '../shared';
|
||||||
import { CLEAR, FORM, FORM_CONTAINER, WHITE } from './constants';
|
|
||||||
import { login } from './login';
|
|
||||||
import { clearNote } from './note';
|
|
||||||
import {
|
import {
|
||||||
selectCollapseButton,
|
selectCollapseButton,
|
||||||
selectForm,
|
selectForm,
|
||||||
selectFormContainer,
|
selectFormContainer,
|
||||||
selectNoteContainer,
|
selectNoteContainer,
|
||||||
} from './utils';
|
} from './utils';
|
||||||
import { commentIcon, compressIcon } from './wrapper_icons';
|
import { collapseButton, commentIcon, compressIcon } from './wrapper_icons';
|
||||||
|
|
||||||
const form = content => `
|
const form = content => `
|
||||||
<form id="${FORM}">
|
<form id="${FORM}" novalidate>
|
||||||
${content}
|
${content}
|
||||||
</form>
|
</form>
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const buttonAndForm = ({ content, toggleButton }) => `
|
const buttonAndForm = content => `
|
||||||
<div id="${FORM_CONTAINER}" class="gitlab-form-open">
|
<div id="${FORM_CONTAINER}" class="gitlab-form-open">
|
||||||
${toggleButton}
|
${collapseButton}
|
||||||
${form(content)}
|
${form(content)}
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const addCommentForm = () => {
|
const addForm = nextForm => {
|
||||||
const formWrapper = selectForm();
|
const formWrapper = selectForm();
|
||||||
formWrapper.innerHTML = comment;
|
formWrapper.innerHTML = nextForm;
|
||||||
};
|
};
|
||||||
|
|
||||||
const addLoginForm = () => {
|
|
||||||
const formWrapper = selectForm();
|
|
||||||
formWrapper.innerHTML = login;
|
|
||||||
};
|
|
||||||
|
|
||||||
function logoutUser() {
|
|
||||||
const { localStorage } = window;
|
|
||||||
|
|
||||||
// All the browsers we support have localStorage, so let's silently fail
|
|
||||||
// and go on with the rest of the functionality.
|
|
||||||
try {
|
|
||||||
localStorage.removeItem('token');
|
|
||||||
} catch (err) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
clearNote();
|
|
||||||
addLoginForm();
|
|
||||||
}
|
|
||||||
|
|
||||||
function toggleForm() {
|
function toggleForm() {
|
||||||
const collapseButton = selectCollapseButton();
|
const toggleButton = selectCollapseButton();
|
||||||
const currentForm = selectForm();
|
const currentForm = selectForm();
|
||||||
const formContainer = selectFormContainer();
|
const formContainer = selectFormContainer();
|
||||||
const noteContainer = selectNoteContainer();
|
const noteContainer = selectNoteContainer();
|
||||||
|
@ -84,19 +61,19 @@ function toggleForm() {
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
const nextState = collapseButton.classList.contains('gitlab-collapse-open') ? CLOSED : OPEN;
|
const nextState = toggleButton.classList.contains('gitlab-collapse-open') ? CLOSED : OPEN;
|
||||||
const currentVals = stateVals[nextState];
|
const currentVals = stateVals[nextState];
|
||||||
|
|
||||||
formContainer.classList.replace(...currentVals.containerClasses);
|
formContainer.classList.replace(...currentVals.containerClasses);
|
||||||
formContainer.style.backgroundColor = currentVals.backgroundColor;
|
formContainer.style.backgroundColor = currentVals.backgroundColor;
|
||||||
formContainer.classList.toggle('gitlab-form-open');
|
formContainer.classList.toggle('gitlab-form-open');
|
||||||
currentForm.style.display = currentVals.display;
|
currentForm.style.display = currentVals.display;
|
||||||
collapseButton.classList.replace(...currentVals.buttonClasses);
|
toggleButton.classList.replace(...currentVals.buttonClasses);
|
||||||
collapseButton.innerHTML = currentVals.icon;
|
toggleButton.innerHTML = currentVals.icon;
|
||||||
|
|
||||||
if (noteContainer && noteContainer.innerText.length > 0) {
|
if (noteContainer && noteContainer.innerText.length > 0) {
|
||||||
noteContainer.style.display = currentVals.display;
|
noteContainer.style.display = currentVals.display;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export { addCommentForm, addLoginForm, buttonAndForm, logoutUser, toggleForm };
|
export { addForm, buttonAndForm, toggleForm };
|
||||||
|
|
|
@ -1,7 +1,8 @@
|
||||||
import './styles/toolbar.css';
|
import './styles/toolbar.css';
|
||||||
|
|
||||||
import { buttonAndForm, note, selectContainer, REVIEW_CONTAINER } from './components';
|
import { buttonAndForm, note, selectForm, selectContainer } from './components';
|
||||||
import { debounce, eventLookup, getInitialView, initializeState, updateWindowSize } from './store';
|
import { REVIEW_CONTAINER } from './shared';
|
||||||
|
import { eventLookup, getInitialView, initializeGlobalListeners, initializeState } from './store';
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|
||||||
|
@ -20,7 +21,7 @@ import { debounce, eventLookup, getInitialView, initializeState, updateWindowSiz
|
||||||
window.addEventListener('load', () => {
|
window.addEventListener('load', () => {
|
||||||
initializeState(window, document);
|
initializeState(window, document);
|
||||||
|
|
||||||
const mainContent = buttonAndForm(getInitialView(window));
|
const mainContent = buttonAndForm(getInitialView());
|
||||||
const container = document.createElement('div');
|
const container = document.createElement('div');
|
||||||
container.setAttribute('id', REVIEW_CONTAINER);
|
container.setAttribute('id', REVIEW_CONTAINER);
|
||||||
container.insertAdjacentHTML('beforeend', note);
|
container.insertAdjacentHTML('beforeend', note);
|
||||||
|
@ -29,8 +30,22 @@ window.addEventListener('load', () => {
|
||||||
document.body.insertBefore(container, document.body.firstChild);
|
document.body.insertBefore(container, document.body.firstChild);
|
||||||
|
|
||||||
selectContainer().addEventListener('click', event => {
|
selectContainer().addEventListener('click', event => {
|
||||||
eventLookup(event)();
|
eventLookup(event.target.id)();
|
||||||
});
|
});
|
||||||
|
|
||||||
window.addEventListener('resize', debounce(updateWindowSize.bind(null, window), 200));
|
selectForm().addEventListener('submit', event => {
|
||||||
|
// this is important to prevent the form from adding data
|
||||||
|
// as URL params and inadvertently revealing secrets
|
||||||
|
event.preventDefault();
|
||||||
|
|
||||||
|
const id =
|
||||||
|
event.target.querySelector('.gitlab-button-wrapper') &&
|
||||||
|
event.target.querySelector('.gitlab-button-wrapper').getElementsByTagName('button')[0] &&
|
||||||
|
event.target.querySelector('.gitlab-button-wrapper').getElementsByTagName('button')[0].id;
|
||||||
|
|
||||||
|
// even if this is called with false, it's ok; it will get the default no-op
|
||||||
|
eventLookup(id)();
|
||||||
|
});
|
||||||
|
|
||||||
|
initializeGlobalListeners();
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,14 +1,17 @@
|
||||||
// component selectors
|
// component selectors
|
||||||
|
const CHANGE_MR_ID_BUTTON = 'gitlab-change-mr';
|
||||||
const COLLAPSE_BUTTON = 'gitlab-collapse';
|
const COLLAPSE_BUTTON = 'gitlab-collapse';
|
||||||
const COMMENT_BOX = 'gitlab-comment';
|
const COMMENT_BOX = 'gitlab-comment';
|
||||||
const COMMENT_BUTTON = 'gitlab-comment-button';
|
const COMMENT_BUTTON = 'gitlab-comment-button';
|
||||||
const FORM = 'gitlab-form';
|
const FORM = 'gitlab-form';
|
||||||
const FORM_CONTAINER = 'gitlab-form-wrapper';
|
const FORM_CONTAINER = 'gitlab-form-wrapper';
|
||||||
const LOGIN = 'gitlab-login';
|
const LOGIN = 'gitlab-login-button';
|
||||||
const LOGOUT = 'gitlab-logout-button';
|
const LOGOUT = 'gitlab-logout-button';
|
||||||
|
const MR_ID = 'gitlab-submit-mr';
|
||||||
|
const MR_ID_BUTTON = 'gitlab-submit-mr-button';
|
||||||
const NOTE = 'gitlab-validation-note';
|
const NOTE = 'gitlab-validation-note';
|
||||||
const NOTE_CONTAINER = 'gitlab-note-wrapper';
|
const NOTE_CONTAINER = 'gitlab-note-wrapper';
|
||||||
const REMEMBER_TOKEN = 'gitlab-remember_token';
|
const REMEMBER_ITEM = 'gitlab-remember-item';
|
||||||
const REVIEW_CONTAINER = 'gitlab-review-container';
|
const REVIEW_CONTAINER = 'gitlab-review-container';
|
||||||
const TOKEN_BOX = 'gitlab-token';
|
const TOKEN_BOX = 'gitlab-token';
|
||||||
|
|
||||||
|
@ -21,6 +24,7 @@ const RED = 'rgba(219, 59, 33, 1)';
|
||||||
const WHITE = 'rgba(250, 250, 250, 1)';
|
const WHITE = 'rgba(250, 250, 250, 1)';
|
||||||
|
|
||||||
export {
|
export {
|
||||||
|
CHANGE_MR_ID_BUTTON,
|
||||||
COLLAPSE_BUTTON,
|
COLLAPSE_BUTTON,
|
||||||
COMMENT_BOX,
|
COMMENT_BOX,
|
||||||
COMMENT_BUTTON,
|
COMMENT_BUTTON,
|
||||||
|
@ -28,9 +32,11 @@ export {
|
||||||
FORM_CONTAINER,
|
FORM_CONTAINER,
|
||||||
LOGIN,
|
LOGIN,
|
||||||
LOGOUT,
|
LOGOUT,
|
||||||
|
MR_ID,
|
||||||
|
MR_ID_BUTTON,
|
||||||
NOTE,
|
NOTE,
|
||||||
NOTE_CONTAINER,
|
NOTE_CONTAINER,
|
||||||
REMEMBER_TOKEN,
|
REMEMBER_ITEM,
|
||||||
REVIEW_CONTAINER,
|
REVIEW_CONTAINER,
|
||||||
TOKEN_BOX,
|
TOKEN_BOX,
|
||||||
BLACK,
|
BLACK,
|
|
@ -0,0 +1,49 @@
|
||||||
|
import {
|
||||||
|
CHANGE_MR_ID_BUTTON,
|
||||||
|
COLLAPSE_BUTTON,
|
||||||
|
COMMENT_BOX,
|
||||||
|
COMMENT_BUTTON,
|
||||||
|
FORM,
|
||||||
|
FORM_CONTAINER,
|
||||||
|
LOGIN,
|
||||||
|
LOGOUT,
|
||||||
|
MR_ID,
|
||||||
|
MR_ID_BUTTON,
|
||||||
|
NOTE,
|
||||||
|
NOTE_CONTAINER,
|
||||||
|
REMEMBER_ITEM,
|
||||||
|
REVIEW_CONTAINER,
|
||||||
|
TOKEN_BOX,
|
||||||
|
BLACK,
|
||||||
|
CLEAR,
|
||||||
|
MUTED,
|
||||||
|
RED,
|
||||||
|
WHITE,
|
||||||
|
} from './constants';
|
||||||
|
|
||||||
|
import { localStorage, sessionStorage } from './storage_utils';
|
||||||
|
|
||||||
|
export {
|
||||||
|
localStorage,
|
||||||
|
sessionStorage,
|
||||||
|
CHANGE_MR_ID_BUTTON,
|
||||||
|
COLLAPSE_BUTTON,
|
||||||
|
COMMENT_BOX,
|
||||||
|
COMMENT_BUTTON,
|
||||||
|
FORM,
|
||||||
|
FORM_CONTAINER,
|
||||||
|
LOGIN,
|
||||||
|
LOGOUT,
|
||||||
|
MR_ID,
|
||||||
|
MR_ID_BUTTON,
|
||||||
|
NOTE,
|
||||||
|
NOTE_CONTAINER,
|
||||||
|
REMEMBER_ITEM,
|
||||||
|
REVIEW_CONTAINER,
|
||||||
|
TOKEN_BOX,
|
||||||
|
BLACK,
|
||||||
|
CLEAR,
|
||||||
|
MUTED,
|
||||||
|
RED,
|
||||||
|
WHITE,
|
||||||
|
};
|
|
@ -0,0 +1,42 @@
|
||||||
|
import { setUsingGracefulStorageFlag } from '../store/state';
|
||||||
|
|
||||||
|
const TEST_KEY = 'gitlab-storage-test';
|
||||||
|
|
||||||
|
const createStorageStub = () => {
|
||||||
|
const items = {};
|
||||||
|
|
||||||
|
return {
|
||||||
|
getItem(key) {
|
||||||
|
return items[key];
|
||||||
|
},
|
||||||
|
setItem(key, value) {
|
||||||
|
items[key] = value;
|
||||||
|
},
|
||||||
|
removeItem(key) {
|
||||||
|
delete items[key];
|
||||||
|
},
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const hasStorageSupport = storage => {
|
||||||
|
// Support test taken from https://stackoverflow.com/a/11214467/1708147
|
||||||
|
try {
|
||||||
|
storage.setItem(TEST_KEY, TEST_KEY);
|
||||||
|
storage.removeItem(TEST_KEY);
|
||||||
|
setUsingGracefulStorageFlag(true);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
} catch (err) {
|
||||||
|
setUsingGracefulStorageFlag(false);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const useGracefulStorage = storage =>
|
||||||
|
// If a browser does not support local storage, let's return a graceful implementation.
|
||||||
|
hasStorageSupport(storage) ? storage : createStorageStub();
|
||||||
|
|
||||||
|
const localStorage = useGracefulStorage(window.localStorage);
|
||||||
|
const sessionStorage = useGracefulStorage(window.sessionStorage);
|
||||||
|
|
||||||
|
export { localStorage, sessionStorage };
|
|
@ -1,20 +1,37 @@
|
||||||
import {
|
import {
|
||||||
|
addMr,
|
||||||
authorizeUser,
|
authorizeUser,
|
||||||
|
changeSelectedMr,
|
||||||
logoutUser,
|
logoutUser,
|
||||||
postComment,
|
postComment,
|
||||||
|
saveComment,
|
||||||
toggleForm,
|
toggleForm,
|
||||||
|
} from '../components';
|
||||||
|
|
||||||
|
import {
|
||||||
|
CHANGE_MR_ID_BUTTON,
|
||||||
COLLAPSE_BUTTON,
|
COLLAPSE_BUTTON,
|
||||||
COMMENT_BUTTON,
|
COMMENT_BUTTON,
|
||||||
LOGIN,
|
LOGIN,
|
||||||
LOGOUT,
|
LOGOUT,
|
||||||
} from '../components';
|
MR_ID_BUTTON,
|
||||||
|
} from '../shared';
|
||||||
|
|
||||||
import { state } from './state';
|
import { state } from './state';
|
||||||
|
import debounce from './utils';
|
||||||
|
|
||||||
const noop = () => {};
|
const noop = () => {};
|
||||||
|
|
||||||
const eventLookup = ({ target: { id } }) => {
|
// State needs to be bound here to be acted on
|
||||||
|
// because these are called by click events and
|
||||||
|
// as such are called with only the `event` object
|
||||||
|
const eventLookup = id => {
|
||||||
switch (id) {
|
switch (id) {
|
||||||
|
case CHANGE_MR_ID_BUTTON:
|
||||||
|
return () => {
|
||||||
|
saveComment();
|
||||||
|
changeSelectedMr(state);
|
||||||
|
};
|
||||||
case COLLAPSE_BUTTON:
|
case COLLAPSE_BUTTON:
|
||||||
return toggleForm;
|
return toggleForm;
|
||||||
case COMMENT_BUTTON:
|
case COMMENT_BUTTON:
|
||||||
|
@ -22,7 +39,12 @@ const eventLookup = ({ target: { id } }) => {
|
||||||
case LOGIN:
|
case LOGIN:
|
||||||
return authorizeUser.bind(null, state);
|
return authorizeUser.bind(null, state);
|
||||||
case LOGOUT:
|
case LOGOUT:
|
||||||
return logoutUser;
|
return () => {
|
||||||
|
saveComment();
|
||||||
|
logoutUser(state);
|
||||||
|
};
|
||||||
|
case MR_ID_BUTTON:
|
||||||
|
return addMr.bind(null, state);
|
||||||
default:
|
default:
|
||||||
return noop;
|
return noop;
|
||||||
}
|
}
|
||||||
|
@ -33,4 +55,19 @@ const updateWindowSize = wind => {
|
||||||
state.innerHeight = wind.innerHeight;
|
state.innerHeight = wind.innerHeight;
|
||||||
};
|
};
|
||||||
|
|
||||||
export { eventLookup, updateWindowSize };
|
const initializeGlobalListeners = () => {
|
||||||
|
window.addEventListener('resize', debounce(updateWindowSize.bind(null, window), 200));
|
||||||
|
window.addEventListener('beforeunload', event => {
|
||||||
|
if (state.usingGracefulStorage) {
|
||||||
|
// if there is no browser storage support, reloading will lose the comment; this way, the user will be warned
|
||||||
|
// we assign the return value because it is required by Chrome see: https://developer.mozilla.org/en-US/docs/Web/API/WindowEventHandlers/onbeforeunload#Example,
|
||||||
|
event.preventDefault();
|
||||||
|
/* eslint-disable-next-line no-param-reassign */
|
||||||
|
event.returnValue = '';
|
||||||
|
}
|
||||||
|
|
||||||
|
saveComment();
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export { eventLookup, initializeGlobalListeners };
|
||||||
|
|
|
@ -1,5 +1,11 @@
|
||||||
import { eventLookup, updateWindowSize } from './events';
|
import { eventLookup, initializeGlobalListeners } from './events';
|
||||||
import { getInitialView, initializeState } from './state';
|
import { nextView, getInitialView, initializeState, setUsingGracefulStorageFlag } from './state';
|
||||||
import debounce from './utils';
|
|
||||||
|
|
||||||
export { debounce, eventLookup, getInitialView, initializeState, updateWindowSize };
|
export {
|
||||||
|
eventLookup,
|
||||||
|
getInitialView,
|
||||||
|
initializeGlobalListeners,
|
||||||
|
initializeState,
|
||||||
|
nextView,
|
||||||
|
setUsingGracefulStorageFlag,
|
||||||
|
};
|
||||||
|
|
|
@ -1,8 +1,9 @@
|
||||||
import { comment, login, collapseButton } from '../components';
|
import { comment, login, mrForm } from '../components';
|
||||||
|
import { localStorage, COMMENT_BOX, LOGIN, MR_ID } from '../shared';
|
||||||
|
|
||||||
const state = {
|
const state = {
|
||||||
browser: '',
|
browser: '',
|
||||||
href: '',
|
usingGracefulStorage: '',
|
||||||
innerWidth: '',
|
innerWidth: '',
|
||||||
innerHeight: '',
|
innerHeight: '',
|
||||||
mergeRequestId: '',
|
mergeRequestId: '',
|
||||||
|
@ -23,11 +24,31 @@ const getBrowserId = sUsrAg => {
|
||||||
return aKeys[nIdx];
|
return aKeys[nIdx];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const nextView = (appState, form = 'none') => {
|
||||||
|
const formsList = {
|
||||||
|
[COMMENT_BOX]: currentState => (currentState.token ? mrForm : login),
|
||||||
|
[LOGIN]: currentState => (currentState.mergeRequestId ? comment(currentState) : mrForm),
|
||||||
|
[MR_ID]: currentState => (currentState.token ? comment(currentState) : login),
|
||||||
|
none: currentState => {
|
||||||
|
if (!currentState.token) {
|
||||||
|
return login;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (currentState.token && !currentState.mergeRequestId) {
|
||||||
|
return mrForm;
|
||||||
|
}
|
||||||
|
|
||||||
|
return comment(currentState);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
return formsList[form](appState);
|
||||||
|
};
|
||||||
|
|
||||||
const initializeState = (wind, doc) => {
|
const initializeState = (wind, doc) => {
|
||||||
const {
|
const {
|
||||||
innerWidth,
|
innerWidth,
|
||||||
innerHeight,
|
innerHeight,
|
||||||
location: { href },
|
|
||||||
navigator: { platform, userAgent },
|
navigator: { platform, userAgent },
|
||||||
} = wind;
|
} = wind;
|
||||||
|
|
||||||
|
@ -39,7 +60,6 @@ const initializeState = (wind, doc) => {
|
||||||
// This mutates our default state object above. It's weird but it makes the linter happy.
|
// This mutates our default state object above. It's weird but it makes the linter happy.
|
||||||
Object.assign(state, {
|
Object.assign(state, {
|
||||||
browser,
|
browser,
|
||||||
href,
|
|
||||||
innerWidth,
|
innerWidth,
|
||||||
innerHeight,
|
innerHeight,
|
||||||
mergeRequestId,
|
mergeRequestId,
|
||||||
|
@ -49,30 +69,27 @@ const initializeState = (wind, doc) => {
|
||||||
projectPath,
|
projectPath,
|
||||||
userAgent,
|
userAgent,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
return state;
|
||||||
};
|
};
|
||||||
|
|
||||||
function getInitialView({ localStorage }) {
|
const getInitialView = () => {
|
||||||
const loginView = {
|
|
||||||
content: login,
|
|
||||||
toggleButton: collapseButton,
|
|
||||||
};
|
|
||||||
|
|
||||||
const commentView = {
|
|
||||||
content: comment,
|
|
||||||
toggleButton: collapseButton,
|
|
||||||
};
|
|
||||||
|
|
||||||
try {
|
|
||||||
const token = localStorage.getItem('token');
|
const token = localStorage.getItem('token');
|
||||||
|
const mrId = localStorage.getItem('mergeRequestId');
|
||||||
|
|
||||||
if (token) {
|
if (token) {
|
||||||
state.token = token;
|
state.token = token;
|
||||||
return commentView;
|
|
||||||
}
|
|
||||||
return loginView;
|
|
||||||
} catch (err) {
|
|
||||||
return loginView;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export { initializeState, getInitialView, state };
|
if (mrId) {
|
||||||
|
state.mergeRequestId = mrId;
|
||||||
|
}
|
||||||
|
|
||||||
|
return nextView(state);
|
||||||
|
};
|
||||||
|
|
||||||
|
const setUsingGracefulStorageFlag = flag => {
|
||||||
|
state.usingGracefulStorage = !flag;
|
||||||
|
};
|
||||||
|
|
||||||
|
export { initializeState, getInitialView, nextView, setUsingGracefulStorageFlag, state };
|
||||||
|
|
|
@ -107,10 +107,14 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.gitlab-button-wrapper {
|
.gitlab-button-wrapper {
|
||||||
margin-top: 1rem;
|
margin-top: 0.5rem;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: baseline;
|
align-items: baseline;
|
||||||
justify-content: flex-end;
|
/*
|
||||||
|
this makes sure the hit enter to submit picks the correct button
|
||||||
|
on the comment view
|
||||||
|
*/
|
||||||
|
flex-direction: row-reverse;
|
||||||
}
|
}
|
||||||
|
|
||||||
.gitlab-collapse {
|
.gitlab-collapse {
|
||||||
|
@ -155,6 +159,12 @@
|
||||||
text-decoration: underline;
|
text-decoration: underline;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.gitlab-link-button {
|
||||||
|
border: none;
|
||||||
|
cursor: pointer;
|
||||||
|
padding: 0 .15rem;
|
||||||
|
}
|
||||||
|
|
||||||
.gitlab-message {
|
.gitlab-message {
|
||||||
padding: .25rem 0;
|
padding: .25rem 0;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
|
@ -165,7 +175,7 @@
|
||||||
font-size: .7rem;
|
font-size: .7rem;
|
||||||
line-height: 1rem;
|
line-height: 1rem;
|
||||||
color: #666;
|
color: #666;
|
||||||
margin-bottom: 0;
|
margin-bottom: .5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.gitlab-input {
|
.gitlab-input {
|
||||||
|
|
|
@ -0,0 +1,5 @@
|
||||||
|
---
|
||||||
|
title: Add MR form to Visual Review (EE) runtime configuration
|
||||||
|
merge_request: 30481
|
||||||
|
author:
|
||||||
|
type: changed
|
Loading…
Reference in New Issue