Skip to content
Snippets Groups Projects
Commit c96adfcd authored by Scott Hampton's avatar Scott Hampton
Browse files

Move visual review toolbar to NPM

Remove the visual review toolbar code
in favor of using the NPM package.
parent 8f797950
No related branches found
No related tags found
No related merge requests found
Showing
with 0 additions and 963 deletions
import { nextView } from '../store';
import { localStorage, COMMENT_BOX, LOGOUT, STORAGE_MR_ID, STORAGE_TOKEN } from '../shared';
import { clearNote } from './note';
import { buttonClearStyles } from './utils';
import { addForm } from './wrapper';
import { changeSelectedMr, selectedMrNote } from './comment_mr_note';
import postComment from './comment_post';
import { saveComment, getSavedComment } from './comment_storage';
const comment = state => {
const savedComment = getSavedComment();
return `
<div>
<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>
</div>
<div class="gitlab-button-wrapper">
<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>
`;
};
// This function is here becaause it is called only from the comment view
// If we reach a design where we can logout from multiple views, promote this
// to it's own package
const logoutUser = state => {
localStorage.removeItem(STORAGE_TOKEN);
localStorage.removeItem(STORAGE_MR_ID);
state.token = '';
state.mergeRequestId = '';
clearNote();
addForm(nextView(state, COMMENT_BOX));
};
export { changeSelectedMr, comment, logoutUser, postComment, saveComment };
import { nextView } from '../store';
import { localStorage, CHANGE_MR_ID_BUTTON, COMMENT_BOX, STORAGE_MR_ID } 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(STORAGE_MR_ID);
state.mergeRequestId = '';
};
const changeSelectedMr = state => {
clearMrId(state);
clearNote();
addForm(nextView(state, COMMENT_BOX));
};
export { changeSelectedMr, selectedMrNote };
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;
import { selectCommentBox } from './utils';
import { sessionStorage, STORAGE_COMMENT } from '../shared';
const getSavedComment = () => sessionStorage.getItem(STORAGE_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(STORAGE_COMMENT, currentComment.value);
}
};
const clearSavedComment = () => {
sessionStorage.removeItem(STORAGE_COMMENT);
};
export { getSavedComment, saveComment, clearSavedComment };
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 };
import { changeSelectedMr, comment, logoutUser, postComment, saveComment } from './comment';
import { authorizeUser, login } from './login';
import { addMr, mrForm } from './mr_id';
import { note } from './note';
import { selectContainer, selectForm } from './utils';
import { buttonAndForm, toggleForm } from './wrapper';
export {
addMr,
authorizeUser,
buttonAndForm,
changeSelectedMr,
comment,
login,
logoutUser,
mrForm,
note,
postComment,
saveComment,
selectContainer,
selectForm,
toggleForm,
};
import { nextView } from '../store';
import { localStorage, LOGIN, TOKEN_BOX, STORAGE_TOKEN } from '../shared';
import { clearNote, postError } from './note';
import { rememberBox, submitButton } from './form_elements';
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 = `
<div>
<label for="${TOKEN_BOX}" class="gitlab-label">${labelText}</label>
<input class="gitlab-input" type="password" id="${TOKEN_BOX}" name="${TOKEN_BOX}" autocomplete="current-password" aria-required="true">
</div>
${rememberBox()}
${submitButton(LOGIN)}
`;
const storeToken = (token, state) => {
const rememberMe = selectRemember().checked;
if (rememberMe) {
localStorage.setItem(STORAGE_TOKEN, token);
}
state.token = token;
};
const authorizeUser = state => {
// Clear any old errors
clearNote(TOKEN_BOX);
const token = selectToken().value;
if (!token) {
/* eslint-disable-next-line @gitlab/i18n/no-non-i18n-strings */
postError('Please enter your token.', TOKEN_BOX);
return;
}
storeToken(token, state);
addForm(nextView(state, LOGIN));
};
export { authorizeUser, login, storeToken };
import { nextView } from '../store';
import { MR_ID, MR_ID_BUTTON, STORAGE_MR_ID, 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(STORAGE_MR_ID, 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 };
import { NOTE, NOTE_CONTAINER, RED } from '../shared';
import { selectById, selectNote, selectNoteContainer } from './utils';
const note = `
<div id="${NOTE_CONTAINER}" style="visibility: hidden;">
<p id="${NOTE}" class="gitlab-message"></p>
</div>
`;
const clearNote = inputId => {
const currentNote = selectNote();
const noteContainer = selectNoteContainer();
currentNote.innerText = '';
currentNote.style.color = '';
noteContainer.style.visibility = 'hidden';
if (inputId) {
const field = document.getElementById(inputId);
field.style.borderColor = '';
}
};
const postError = (message, inputId) => {
const currentNote = selectNote();
const noteContainer = selectNoteContainer();
const field = selectById(inputId);
field.style.borderColor = RED;
currentNote.style.color = RED;
currentNote.innerText = message;
noteContainer.style.visibility = 'visible';
setTimeout(clearNote.bind(null, inputId), 5000);
};
export { clearNote, note, postError };
/* global document */
import {
COLLAPSE_BUTTON,
COMMENT_BOX,
COMMENT_BUTTON,
FORM,
FORM_CONTAINER,
MR_ID,
NOTE,
NOTE_CONTAINER,
REMEMBER_ITEM,
REVIEW_CONTAINER,
TOKEN_BOX,
} from '../shared';
// this style must be applied inline in a handful of components
/* eslint-disable-next-line @gitlab/i18n/no-non-i18n-strings */
const buttonClearStyles = `
-webkit-appearance: none;
`;
// selector functions to abstract out a little
const selectById = id => document.getElementById(id);
const selectCollapseButton = () => document.getElementById(COLLAPSE_BUTTON);
const selectCommentBox = () => document.getElementById(COMMENT_BOX);
const selectCommentButton = () => document.getElementById(COMMENT_BUTTON);
const selectContainer = () => document.getElementById(REVIEW_CONTAINER);
const selectForm = () => document.getElementById(FORM);
const selectFormContainer = () => document.getElementById(FORM_CONTAINER);
const selectMrBox = () => document.getElementById(MR_ID);
const selectNote = () => document.getElementById(NOTE);
const selectNoteContainer = () => document.getElementById(NOTE_CONTAINER);
const selectRemember = () => document.getElementById(REMEMBER_ITEM);
const selectToken = () => document.getElementById(TOKEN_BOX);
export {
buttonClearStyles,
selectById,
selectCollapseButton,
selectContainer,
selectCommentBox,
selectCommentButton,
selectForm,
selectFormContainer,
selectMrBox,
selectNote,
selectNoteContainer,
selectRemember,
selectToken,
};
import { CLEAR, FORM, FORM_CONTAINER, WHITE } from '../shared';
import {
selectCollapseButton,
selectForm,
selectFormContainer,
selectNoteContainer,
} from './utils';
import { collapseButton, commentIcon, compressIcon } from './wrapper_icons';
const form = content => `
<form id="${FORM}" novalidate>
${content}
</form>
`;
const buttonAndForm = content => `
<div id="${FORM_CONTAINER}" class="gitlab-form-open">
${collapseButton}
${form(content)}
</div>
`;
const addForm = nextForm => {
const formWrapper = selectForm();
formWrapper.innerHTML = nextForm;
};
function toggleForm() {
const toggleButton = selectCollapseButton();
const currentForm = selectForm();
const formContainer = selectFormContainer();
const noteContainer = selectNoteContainer();
const OPEN = 'open';
const CLOSED = 'closed';
/*
You may wonder why we spread the arrays before we reverse them.
In the immortal words of MDN,
Careful: reverse is destructive. It also changes the original array
*/
const openButtonClasses = ['gitlab-collapse-closed', 'gitlab-collapse-open'];
const closedButtonClasses = [...openButtonClasses].reverse();
const openContainerClasses = ['gitlab-wrapper-closed', 'gitlab-wrapper-open'];
const closedContainerClasses = [...openContainerClasses].reverse();
const stateVals = {
[OPEN]: {
buttonClasses: openButtonClasses,
containerClasses: openContainerClasses,
icon: compressIcon,
display: 'flex',
backgroundColor: WHITE,
},
[CLOSED]: {
buttonClasses: closedButtonClasses,
containerClasses: closedContainerClasses,
icon: commentIcon,
display: 'none',
backgroundColor: CLEAR,
},
};
const nextState = toggleButton.classList.contains('gitlab-collapse-open') ? CLOSED : OPEN;
const currentVals = stateVals[nextState];
formContainer.classList.replace(...currentVals.containerClasses);
formContainer.style.backgroundColor = currentVals.backgroundColor;
formContainer.classList.toggle('gitlab-form-open');
currentForm.style.display = currentVals.display;
toggleButton.classList.replace(...currentVals.buttonClasses);
toggleButton.innerHTML = currentVals.icon;
if (noteContainer && noteContainer.innerText.length > 0) {
noteContainer.style.display = currentVals.display;
}
}
export { addForm, buttonAndForm, toggleForm };
import { buttonClearStyles } from './utils';
const commentIcon = `
<svg width="16" height="16" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><title>icn/comment</title><path d="M4 11.132l1.446-.964A1 1 0 0 1 6 10h5a1 1 0 0 0 1-1V5a1 1 0 0 0-1-1H5a1 1 0 0 0-1 1v6.132zM6.303 12l-2.748 1.832A1 1 0 0 1 2 13V5a3 3 0 0 1 3-3h6a3 3 0 0 1 3 3v4a3 3 0 0 1-3 3H6.303z" id="gitlab-comment-icon"/></svg>
`;
const compressIcon = `
<svg width="16" height="16" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><title>icn/compress</title><path d="M5.27 12.182l-1.562 1.561a1 1 0 0 1-1.414 0h-.001a1 1 0 0 1 0-1.415l1.56-1.56L2.44 9.353a.5.5 0 0 1 .353-.854H7.09a.5.5 0 0 1 .5.5v4.294a.5.5 0 0 1-.853.353l-1.467-1.465zm6.911-6.914l1.464 1.464a.5.5 0 0 1-.353.854H8.999a.5.5 0 0 1-.5-.5V2.793a.5.5 0 0 1 .854-.354l1.414 1.415 1.56-1.561a1 1 0 1 1 1.415 1.414l-1.561 1.56z" id="gitlab-compress-icon"/></svg>
`;
const collapseButton = `
<button id='gitlab-collapse' style='${buttonClearStyles}' class='gitlab-button gitlab-button-secondary gitlab-collapse gitlab-collapse-open'>${compressIcon}</button>
`;
export { commentIcon, compressIcon, collapseButton };
import './styles/toolbar.css';
import { buttonAndForm, note, selectForm, selectContainer } from './components';
import { REVIEW_CONTAINER } from './shared';
import { eventLookup, getInitialView, initializeGlobalListeners, initializeState } from './store';
/*
Welcome to the visual review toolbar files. A few useful notes:
- These files build a static script that is served from our webpack
assets folder. (https://gitlab.com/assets/webpack/visual_review_toolbar.js)
- To compile this file, run `yarn webpack-vrt`.
- Vue is not used in these files because we do not want to ask users to
install another library at this time. It's all pure vanilla javascript.
*/
window.addEventListener('load', () => {
initializeState(window, document);
const mainContent = buttonAndForm(getInitialView());
const container = document.createElement('div');
container.setAttribute('id', REVIEW_CONTAINER);
container.insertAdjacentHTML('beforeend', note);
container.insertAdjacentHTML('beforeend', mainContent);
document.body.insertBefore(container, document.body.firstChild);
selectContainer().addEventListener('click', event => {
eventLookup(event.target.id)();
});
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();
});
// component selectors
const CHANGE_MR_ID_BUTTON = 'gitlab-change-mr';
const COLLAPSE_BUTTON = 'gitlab-collapse';
const COMMENT_BOX = 'gitlab-comment';
const COMMENT_BUTTON = 'gitlab-comment-button';
const FORM = 'gitlab-form';
const FORM_CONTAINER = 'gitlab-form-wrapper';
const LOGIN = 'gitlab-login-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_CONTAINER = 'gitlab-note-wrapper';
const REMEMBER_ITEM = 'gitlab-remember-item';
const REVIEW_CONTAINER = 'gitlab-review-container';
const TOKEN_BOX = 'gitlab-token';
// Storage keys
const STORAGE_PREFIX = '--gitlab'; // Using `--` to make the prefix more unique
const STORAGE_MR_ID = `${STORAGE_PREFIX}-merge-request-id`;
const STORAGE_TOKEN = `${STORAGE_PREFIX}-token`;
const STORAGE_COMMENT = `${STORAGE_PREFIX}-comment`;
// colors — these are applied programmatically
// rest of styles belong in ./styles
const BLACK = 'rgba(46, 46, 46, 1)';
const CLEAR = 'rgba(255, 255, 255, 0)';
const MUTED = 'rgba(223, 223, 223, 0.5)';
const RED = 'rgba(219, 59, 33, 1)';
const WHITE = 'rgba(250, 250, 250, 1)';
export {
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,
STORAGE_MR_ID,
STORAGE_TOKEN,
STORAGE_COMMENT,
BLACK,
CLEAR,
MUTED,
RED,
WHITE,
};
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,
STORAGE_MR_ID,
STORAGE_TOKEN,
STORAGE_COMMENT,
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,
STORAGE_MR_ID,
STORAGE_TOKEN,
STORAGE_COMMENT,
BLACK,
CLEAR,
MUTED,
RED,
WHITE,
};
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 };
import {
addMr,
authorizeUser,
changeSelectedMr,
logoutUser,
postComment,
saveComment,
toggleForm,
} from '../components';
import {
CHANGE_MR_ID_BUTTON,
COLLAPSE_BUTTON,
COMMENT_BUTTON,
LOGIN,
LOGOUT,
MR_ID_BUTTON,
} from '../shared';
import { state } from './state';
import debounce from './utils';
const noop = () => {};
// 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) {
case CHANGE_MR_ID_BUTTON:
return () => {
saveComment();
changeSelectedMr(state);
};
case COLLAPSE_BUTTON:
return toggleForm;
case COMMENT_BUTTON:
return postComment.bind(null, state);
case LOGIN:
return authorizeUser.bind(null, state);
case LOGOUT:
return () => {
saveComment();
logoutUser(state);
};
case MR_ID_BUTTON:
return addMr.bind(null, state);
default:
return noop;
}
};
const updateWindowSize = wind => {
state.innerWidth = wind.innerWidth;
state.innerHeight = wind.innerHeight;
};
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 };
import { eventLookup, initializeGlobalListeners } from './events';
import { nextView, getInitialView, initializeState, setUsingGracefulStorageFlag } from './state';
export {
eventLookup,
getInitialView,
initializeGlobalListeners,
initializeState,
nextView,
setUsingGracefulStorageFlag,
};
import { comment, login, mrForm } from '../components';
import { localStorage, COMMENT_BOX, LOGIN, MR_ID, STORAGE_MR_ID, STORAGE_TOKEN } from '../shared';
const state = {
browser: '',
usingGracefulStorage: '',
innerWidth: '',
innerHeight: '',
mergeRequestId: '',
mrUrl: '',
platform: '',
projectId: '',
userAgent: '',
token: '',
};
// adapted from https://developer.mozilla.org/en-US/docs/Web/API/Window/navigator#Example_2_Browser_detect_and_return_an_index
const getBrowserId = sUsrAg => {
/* eslint-disable-next-line @gitlab/i18n/no-non-i18n-strings */
const aKeys = ['MSIE', 'Edge', 'Firefox', 'Safari', 'Chrome', 'Opera'];
let nIdx = aKeys.length - 1;
for (nIdx; nIdx > -1 && sUsrAg.indexOf(aKeys[nIdx]) === -1; nIdx -= 1);
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 {
innerWidth,
innerHeight,
navigator: { platform, userAgent },
} = wind;
const browser = getBrowserId(userAgent);
const scriptEl = doc.getElementById('review-app-toolbar-script');
const { projectId, mergeRequestId, mrUrl, projectPath } = scriptEl.dataset;
// This mutates our default state object above. It's weird but it makes the linter happy.
Object.assign(state, {
browser,
innerWidth,
innerHeight,
mergeRequestId,
mrUrl,
platform,
projectId,
projectPath,
userAgent,
});
return state;
};
const getInitialView = () => {
const token = localStorage.getItem(STORAGE_TOKEN);
const mrId = localStorage.getItem(STORAGE_MR_ID);
if (token) {
state.token = token;
}
if (mrId) {
state.mergeRequestId = mrId;
}
return nextView(state);
};
const setUsingGracefulStorageFlag = flag => {
state.usingGracefulStorage = !flag;
};
export { initializeState, getInitialView, nextView, setUsingGracefulStorageFlag, state };
const debounce = (fn, time) => {
let current;
const debounced = () => {
if (current) {
clearTimeout(current);
}
current = setTimeout(fn, time);
};
return debounced;
};
export default debounce;
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment