From c96adfcd6c9d303f3f7f86e441e57cb3ce8a286e Mon Sep 17 00:00:00 2001 From: shampton Date: Fri, 23 Aug 2019 12:57:21 -0700 Subject: Move visual review toolbar to NPM Remove the visual review toolbar code in favor of using the NPM package. --- .../visual_review_toolbar/components/comment.js | 39 ----- .../components/comment_mr_note.js | 31 ---- .../components/comment_post.js | 145 ---------------- .../components/comment_storage.js | 20 --- .../components/form_elements.js | 17 -- .../visual_review_toolbar/components/index.js | 23 --- .../visual_review_toolbar/components/login.js | 47 ------ .../visual_review_toolbar/components/mr_id.js | 63 ------- .../visual_review_toolbar/components/note.js | 35 ---- .../visual_review_toolbar/components/utils.js | 51 ------ .../visual_review_toolbar/components/wrapper.js | 79 --------- .../components/wrapper_icons.js | 15 -- .../javascripts/visual_review_toolbar/index.js | 51 ------ .../visual_review_toolbar/shared/constants.js | 56 ------ .../visual_review_toolbar/shared/index.js | 55 ------ .../visual_review_toolbar/shared/storage_utils.js | 42 ----- .../visual_review_toolbar/store/events.js | 73 -------- .../visual_review_toolbar/store/index.js | 11 -- .../visual_review_toolbar/store/state.js | 95 ----------- .../visual_review_toolbar/store/utils.js | 15 -- .../visual_review_toolbar/styles/toolbar.css | 188 --------------------- .../66402-use-visual-review-tools-npm-package.yml | 5 + config/webpack.config.js | 7 + config/webpack.config.review_toolbar.js | 58 ------- lib/tasks/gitlab/assets.rake | 6 - package.json | 4 +- yarn.lock | 5 + 27 files changed, 19 insertions(+), 1217 deletions(-) delete mode 100644 app/assets/javascripts/visual_review_toolbar/components/comment.js delete mode 100644 app/assets/javascripts/visual_review_toolbar/components/comment_mr_note.js delete mode 100644 app/assets/javascripts/visual_review_toolbar/components/comment_post.js delete mode 100644 app/assets/javascripts/visual_review_toolbar/components/comment_storage.js delete mode 100644 app/assets/javascripts/visual_review_toolbar/components/form_elements.js delete mode 100644 app/assets/javascripts/visual_review_toolbar/components/index.js delete mode 100644 app/assets/javascripts/visual_review_toolbar/components/login.js delete mode 100644 app/assets/javascripts/visual_review_toolbar/components/mr_id.js delete mode 100644 app/assets/javascripts/visual_review_toolbar/components/note.js delete mode 100644 app/assets/javascripts/visual_review_toolbar/components/utils.js delete mode 100644 app/assets/javascripts/visual_review_toolbar/components/wrapper.js delete mode 100644 app/assets/javascripts/visual_review_toolbar/components/wrapper_icons.js delete mode 100644 app/assets/javascripts/visual_review_toolbar/index.js delete mode 100644 app/assets/javascripts/visual_review_toolbar/shared/constants.js delete mode 100644 app/assets/javascripts/visual_review_toolbar/shared/index.js delete mode 100644 app/assets/javascripts/visual_review_toolbar/shared/storage_utils.js delete mode 100644 app/assets/javascripts/visual_review_toolbar/store/events.js delete mode 100644 app/assets/javascripts/visual_review_toolbar/store/index.js delete mode 100644 app/assets/javascripts/visual_review_toolbar/store/state.js delete mode 100644 app/assets/javascripts/visual_review_toolbar/store/utils.js delete mode 100644 app/assets/javascripts/visual_review_toolbar/styles/toolbar.css create mode 100644 changelogs/unreleased/66402-use-visual-review-tools-npm-package.yml delete mode 100644 config/webpack.config.review_toolbar.js diff --git a/app/assets/javascripts/visual_review_toolbar/components/comment.js b/app/assets/javascripts/visual_review_toolbar/components/comment.js deleted file mode 100644 index a03dc14b319..00000000000 --- a/app/assets/javascripts/visual_review_toolbar/components/comment.js +++ /dev/null @@ -1,39 +0,0 @@ -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 ` -
- - ${selectedMrNote(state)} - -
-
- - -
- `; -}; - -// 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 }; diff --git a/app/assets/javascripts/visual_review_toolbar/components/comment_mr_note.js b/app/assets/javascripts/visual_review_toolbar/components/comment_mr_note.js deleted file mode 100644 index da67763261c..00000000000 --- a/app/assets/javascripts/visual_review_toolbar/components/comment_mr_note.js +++ /dev/null @@ -1,31 +0,0 @@ -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 ` -

- This posts to merge request !${mergeRequestId}. - -

- `; -}; - -const clearMrId = state => { - localStorage.removeItem(STORAGE_MR_ID); - state.mergeRequestId = ''; -}; - -const changeSelectedMr = state => { - clearMrId(state); - clearNote(); - addForm(nextView(state, COMMENT_BOX)); -}; - -export { changeSelectedMr, selectedMrNote }; diff --git a/app/assets/javascripts/visual_review_toolbar/components/comment_post.js b/app/assets/javascripts/visual_review_toolbar/components/comment_post.js deleted file mode 100644 index ee5f2b62425..00000000000 --- a/app/assets/javascripts/visual_review_toolbar/components/comment_post.js +++ /dev/null @@ -1,145 +0,0 @@ -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 -
- Metadata - Posted from ${href} | ${platform} | ${browser} | ${innerWidth} x ${innerHeight}. -

- User agent: ${userAgent} -
- `; - - 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 ${projectPath} !${mergeRequestId} (comment ${commentId})`; - confirmAndClear(feedbackInfo); - }) - .catch(err => { - postError(commentErrors(err), COMMENT_BOX); - resetCommentBox(); - resetCommentButton(); - }); -}; - -export default postComment; diff --git a/app/assets/javascripts/visual_review_toolbar/components/comment_storage.js b/app/assets/javascripts/visual_review_toolbar/components/comment_storage.js deleted file mode 100644 index 49c9400437e..00000000000 --- a/app/assets/javascripts/visual_review_toolbar/components/comment_storage.js +++ /dev/null @@ -1,20 +0,0 @@ -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 }; diff --git a/app/assets/javascripts/visual_review_toolbar/components/form_elements.js b/app/assets/javascripts/visual_review_toolbar/components/form_elements.js deleted file mode 100644 index 608488a6fea..00000000000 --- a/app/assets/javascripts/visual_review_toolbar/components/form_elements.js +++ /dev/null @@ -1,17 +0,0 @@ -import { REMEMBER_ITEM } from '../shared'; -import { buttonClearStyles } from './utils'; - -/* eslint-disable-next-line @gitlab/i18n/no-non-i18n-strings */ -const rememberBox = (rememberText = 'Remember me') => ` -
- - -
-`; - -const submitButton = buttonId => ` -
- -
-`; -export { rememberBox, submitButton }; diff --git a/app/assets/javascripts/visual_review_toolbar/components/index.js b/app/assets/javascripts/visual_review_toolbar/components/index.js deleted file mode 100644 index e88b3637ad8..00000000000 --- a/app/assets/javascripts/visual_review_toolbar/components/index.js +++ /dev/null @@ -1,23 +0,0 @@ -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, -}; diff --git a/app/assets/javascripts/visual_review_toolbar/components/login.js b/app/assets/javascripts/visual_review_toolbar/components/login.js deleted file mode 100644 index 20ab01bc690..00000000000 --- a/app/assets/javascripts/visual_review_toolbar/components/login.js +++ /dev/null @@ -1,47 +0,0 @@ -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 personal access token -`; - -const login = ` -
- - -
- ${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 }; diff --git a/app/assets/javascripts/visual_review_toolbar/components/mr_id.js b/app/assets/javascripts/visual_review_toolbar/components/mr_id.js deleted file mode 100644 index 695b3af8aa0..00000000000 --- a/app/assets/javascripts/visual_review_toolbar/components/mr_id.js +++ /dev/null @@ -1,63 +0,0 @@ -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 = ` -
- - -
- ${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 }; diff --git a/app/assets/javascripts/visual_review_toolbar/components/note.js b/app/assets/javascripts/visual_review_toolbar/components/note.js deleted file mode 100644 index 9cddcb710f2..00000000000 --- a/app/assets/javascripts/visual_review_toolbar/components/note.js +++ /dev/null @@ -1,35 +0,0 @@ -import { NOTE, NOTE_CONTAINER, RED } from '../shared'; -import { selectById, selectNote, selectNoteContainer } from './utils'; - -const note = ` - -`; - -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 }; diff --git a/app/assets/javascripts/visual_review_toolbar/components/utils.js b/app/assets/javascripts/visual_review_toolbar/components/utils.js deleted file mode 100644 index 4ec9bd4a32a..00000000000 --- a/app/assets/javascripts/visual_review_toolbar/components/utils.js +++ /dev/null @@ -1,51 +0,0 @@ -/* 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, -}; diff --git a/app/assets/javascripts/visual_review_toolbar/components/wrapper.js b/app/assets/javascripts/visual_review_toolbar/components/wrapper.js deleted file mode 100644 index fdf8ad7c41f..00000000000 --- a/app/assets/javascripts/visual_review_toolbar/components/wrapper.js +++ /dev/null @@ -1,79 +0,0 @@ -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 => ` -
- ${content} -
-`; - -const buttonAndForm = content => ` -
- ${collapseButton} - ${form(content)} -
-`; - -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 }; diff --git a/app/assets/javascripts/visual_review_toolbar/components/wrapper_icons.js b/app/assets/javascripts/visual_review_toolbar/components/wrapper_icons.js deleted file mode 100644 index b686fd4f5c2..00000000000 --- a/app/assets/javascripts/visual_review_toolbar/components/wrapper_icons.js +++ /dev/null @@ -1,15 +0,0 @@ -import { buttonClearStyles } from './utils'; - -const commentIcon = ` - icn/comment -`; - -const compressIcon = ` - icn/compress -`; - -const collapseButton = ` - -`; - -export { commentIcon, compressIcon, collapseButton }; diff --git a/app/assets/javascripts/visual_review_toolbar/index.js b/app/assets/javascripts/visual_review_toolbar/index.js deleted file mode 100644 index 67b3fadd772..00000000000 --- a/app/assets/javascripts/visual_review_toolbar/index.js +++ /dev/null @@ -1,51 +0,0 @@ -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(); -}); diff --git a/app/assets/javascripts/visual_review_toolbar/shared/constants.js b/app/assets/javascripts/visual_review_toolbar/shared/constants.js deleted file mode 100644 index 0d5b666ab0a..00000000000 --- a/app/assets/javascripts/visual_review_toolbar/shared/constants.js +++ /dev/null @@ -1,56 +0,0 @@ -// 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, -}; diff --git a/app/assets/javascripts/visual_review_toolbar/shared/index.js b/app/assets/javascripts/visual_review_toolbar/shared/index.js deleted file mode 100644 index d8ccb170592..00000000000 --- a/app/assets/javascripts/visual_review_toolbar/shared/index.js +++ /dev/null @@ -1,55 +0,0 @@ -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, -}; diff --git a/app/assets/javascripts/visual_review_toolbar/shared/storage_utils.js b/app/assets/javascripts/visual_review_toolbar/shared/storage_utils.js deleted file mode 100644 index 00456d3536e..00000000000 --- a/app/assets/javascripts/visual_review_toolbar/shared/storage_utils.js +++ /dev/null @@ -1,42 +0,0 @@ -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 }; diff --git a/app/assets/javascripts/visual_review_toolbar/store/events.js b/app/assets/javascripts/visual_review_toolbar/store/events.js deleted file mode 100644 index c9095c77ef1..00000000000 --- a/app/assets/javascripts/visual_review_toolbar/store/events.js +++ /dev/null @@ -1,73 +0,0 @@ -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 }; diff --git a/app/assets/javascripts/visual_review_toolbar/store/index.js b/app/assets/javascripts/visual_review_toolbar/store/index.js deleted file mode 100644 index 07c8dd6f1d2..00000000000 --- a/app/assets/javascripts/visual_review_toolbar/store/index.js +++ /dev/null @@ -1,11 +0,0 @@ -import { eventLookup, initializeGlobalListeners } from './events'; -import { nextView, getInitialView, initializeState, setUsingGracefulStorageFlag } from './state'; - -export { - eventLookup, - getInitialView, - initializeGlobalListeners, - initializeState, - nextView, - setUsingGracefulStorageFlag, -}; diff --git a/app/assets/javascripts/visual_review_toolbar/store/state.js b/app/assets/javascripts/visual_review_toolbar/store/state.js deleted file mode 100644 index b7853bb0723..00000000000 --- a/app/assets/javascripts/visual_review_toolbar/store/state.js +++ /dev/null @@ -1,95 +0,0 @@ -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 }; diff --git a/app/assets/javascripts/visual_review_toolbar/store/utils.js b/app/assets/javascripts/visual_review_toolbar/store/utils.js deleted file mode 100644 index 5cf145351b3..00000000000 --- a/app/assets/javascripts/visual_review_toolbar/store/utils.js +++ /dev/null @@ -1,15 +0,0 @@ -const debounce = (fn, time) => { - let current; - - const debounced = () => { - if (current) { - clearTimeout(current); - } - - current = setTimeout(fn, time); - }; - - return debounced; -}; - -export default debounce; diff --git a/app/assets/javascripts/visual_review_toolbar/styles/toolbar.css b/app/assets/javascripts/visual_review_toolbar/styles/toolbar.css deleted file mode 100644 index d1a8d66ef40..00000000000 --- a/app/assets/javascripts/visual_review_toolbar/styles/toolbar.css +++ /dev/null @@ -1,188 +0,0 @@ -/* - As a standalone script, the toolbar has its own css - */ - -#gitlab-collapse > * { - pointer-events: none; -} - -#gitlab-comment { - background-color: #fafafa; -} - -#gitlab-form { - display: flex; - flex-direction: column; - width: 100%; - margin-bottom: 0; -} - -#gitlab-note-wrapper { - display: flex; - flex-direction: column; - background-color: #fafafa; - border-radius: 4px; - margin-bottom: .5rem; - padding: 1rem; -} - -#gitlab-form-wrapper { - overflow: auto; - display: flex; - flex-direction: row-reverse; - border-radius: 4px; -} - -#gitlab-review-container { - max-width: 22rem; - max-height: 22rem; - overflow: auto; - display: flex; - flex-direction: column; - position: fixed; - bottom: 1rem; - right: 1rem; - font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Noto Sans', Ubuntu, Cantarell, - 'Helvetica Neue', sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol', - 'Noto Color Emoji'; - font-size: .8rem; - font-weight: 400; - color: #2e2e2e; - z-index: 9999; /* toolbar should always be on top */ -} - -.gitlab-wrapper-open { - max-width: 22rem; - max-height: 22rem; -} - -.gitlab-wrapper-closed { - max-width: 3.4rem; - max-height: 3.4rem; -} - -.gitlab-button { - cursor: pointer; - transition: background-color 100ms linear, border-color 100ms linear, color 100ms linear, box-shadow 100ms linear; -} - -.gitlab-button-secondary { - background: none #fafafa; - margin: 0 .5rem; - border: 1px solid #e3e3e3; -} - -.gitlab-button-secondary:hover { - background-color: #f0f0f0; - border-color: #e3e3e3; - color: #2e2e2e; -} - -.gitlab-button-secondary:active { - color: #2e2e2e; - background-color: #e1e1e1; - border-color: #dadada; -} - -.gitlab-button-success:hover { - color: #fff; - background-color: #137e3f; - border-color: #127339; -} - -.gitlab-button-success:active { - background-color: #168f48; - border-color: #12753a; - color: #fff; -} - -.gitlab-button-success { - background-color: #1aaa55; - border: 1px solid #168f48; - color: #fff; -} - -.gitlab-button-wide { - width: 100%; -} - -.gitlab-button-wrapper { - margin-top: 0.5rem; - display: flex; - align-items: baseline; - /* - this makes sure the hit enter to submit picks the correct button - on the comment view - */ - flex-direction: row-reverse; -} - -.gitlab-collapse { - width: 2.4rem; - height: 2.2rem; - margin-left: 1rem; - padding: .5rem; -} - -.gitlab-collapse-closed { - align-self: center; -} - -.gitlab-checkbox-label { - padding: 0 .2rem; -} - -.gitlab-checkbox-wrapper { - display: flex; - align-items: baseline; -} - -.gitlab-form-open { - padding: 1rem; - background-color: #fafafa; -} - -.gitlab-label { - font-weight: 600; - display: inline-block; - width: 100%; -} - -.gitlab-link { - color: #1b69b6; - text-decoration: none; - background-color: transparent; - background-image: none; -} - -.gitlab-link:hover { - text-decoration: underline; -} - -.gitlab-link-button { - border: none; - cursor: pointer; - padding: 0 .15rem; -} - -.gitlab-message { - padding: .25rem 0; - margin: 0; - line-height: 1.2rem; -} - -.gitlab-metadata-note { - font-size: .7rem; - line-height: 1rem; - color: #666; - margin-bottom: .5rem; -} - -.gitlab-input { - width: 100%; - border: 1px solid #dfdfdf; - border-radius: 4px; - padding: .1rem .2rem; - min-height: 2rem; - max-width: 17rem; -} diff --git a/changelogs/unreleased/66402-use-visual-review-tools-npm-package.yml b/changelogs/unreleased/66402-use-visual-review-tools-npm-package.yml new file mode 100644 index 00000000000..46773e12002 --- /dev/null +++ b/changelogs/unreleased/66402-use-visual-review-tools-npm-package.yml @@ -0,0 +1,5 @@ +--- +title: Move visual review toolbar code to NPM +merge_request: 32159 +author: +type: fixed diff --git a/config/webpack.config.js b/config/webpack.config.js index 442b4b4c21e..969a84e85dd 100644 --- a/config/webpack.config.js +++ b/config/webpack.config.js @@ -298,6 +298,13 @@ module.exports = { from: path.join(ROOT_PATH, 'node_modules/pdfjs-dist/cmaps/'), to: path.join(ROOT_PATH, 'public/assets/webpack/cmaps/'), }, + { + from: path.join( + ROOT_PATH, + 'node_modules/@gitlab/visual-review-tools/dist/visual_review_toolbar.js', + ), + to: path.join(ROOT_PATH, 'public/assets/webpack'), + }, ]), // compression can require a lot of compute time and is disabled in CI diff --git a/config/webpack.config.review_toolbar.js b/config/webpack.config.review_toolbar.js deleted file mode 100644 index baaba7ed387..00000000000 --- a/config/webpack.config.review_toolbar.js +++ /dev/null @@ -1,58 +0,0 @@ -const path = require('path'); -const CompressionPlugin = require('compression-webpack-plugin'); - -const ROOT_PATH = path.resolve(__dirname, '..'); -const CACHE_PATH = process.env.WEBPACK_CACHE_PATH || path.join(ROOT_PATH, 'tmp/cache'); -const NO_SOURCEMAPS = process.env.NO_SOURCEMAPS; -const IS_PRODUCTION = process.env.NODE_ENV === 'production'; - -const devtool = IS_PRODUCTION ? 'source-map' : 'cheap-module-eval-source-map'; - -const alias = { - vendor: path.join(ROOT_PATH, 'vendor/assets/javascripts'), - spec: path.join(ROOT_PATH, 'spec/javascripts'), -}; - -module.exports = { - mode: IS_PRODUCTION ? 'production' : 'development', - - context: path.join(ROOT_PATH, 'app/assets/javascripts'), - - name: 'visual_review_toolbar', - - entry: './visual_review_toolbar', - - output: { - path: path.join(ROOT_PATH, 'public/assets/webpack'), - filename: 'visual_review_toolbar.js', - library: 'VisualReviewToolbar', - libraryTarget: 'var', - }, - - resolve: { - alias, - }, - - module: { - rules: [ - { - test: /\.js$/, - loader: 'babel-loader', - options: { - cacheDirectory: path.join(CACHE_PATH, 'babel-loader'), - }, - }, - { - test: /\.css$/, - use: ['style-loader', 'css-loader'], - }, - ], - }, - - plugins: [ - // compression can require a lot of compute time and is disabled in CI - new CompressionPlugin(), - ].filter(Boolean), - - devtool: NO_SOURCEMAPS ? false : devtool, -}; diff --git a/lib/tasks/gitlab/assets.rake b/lib/tasks/gitlab/assets.rake index a07ae3a418a..7a42e4e92a0 100644 --- a/lib/tasks/gitlab/assets.rake +++ b/lib/tasks/gitlab/assets.rake @@ -10,15 +10,9 @@ namespace :gitlab do rake:assets:precompile webpack:compile gitlab:assets:fix_urls - gitlab:assets:compile_vrt ].each(&Gitlab::TaskHelpers.method(:invoke_and_time_task)) end - desc 'GitLab | Assets | Compile visual review toolbar' - task :compile_vrt do - system 'yarn', 'webpack-vrt' - end - desc 'GitLab | Assets | Clean up old compiled frontend assets' task clean: ['rake:assets:clean'] diff --git a/package.json b/package.json index df5f758fa20..384584a944a 100644 --- a/package.json +++ b/package.json @@ -26,8 +26,7 @@ "stylelint-create-utility-map": "node scripts/frontend/stylelint/stylelint-utility-map.js", "test": "node scripts/frontend/test", "webpack": "NODE_OPTIONS=\"--max-old-space-size=3584\" webpack --config config/webpack.config.js", - "webpack-prod": "NODE_OPTIONS=\"--max-old-space-size=3584\" NODE_ENV=production webpack --config config/webpack.config.js", - "webpack-vrt": "NODE_OPTIONS=\"--max-old-space-size=3584\" NODE_ENV=production webpack --config config/webpack.config.review_toolbar.js" + "webpack-prod": "NODE_OPTIONS=\"--max-old-space-size=3584\" NODE_ENV=production webpack --config config/webpack.config.js" }, "dependencies": { "@babel/core": "^7.5.5", @@ -40,6 +39,7 @@ "@gitlab/csslab": "^1.9.0", "@gitlab/svgs": "^1.68.0", "@gitlab/ui": "5.18.0", + "@gitlab/visual-review-tools": "^1.0.0", "apollo-cache-inmemory": "^1.5.1", "apollo-client": "^2.5.1", "apollo-link": "^1.2.11", diff --git a/yarn.lock b/yarn.lock index 4489b10815a..dd85729333a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1021,6 +1021,11 @@ vue "^2.6.10" vue-loader "^15.4.2" +"@gitlab/visual-review-tools@^1.0.0": + version "1.0.0" + resolved "https://registry.yarnpkg.com/@gitlab/visual-review-tools/-/visual-review-tools-1.0.0.tgz#6012e0a19797c1f5dad34ccf4dacdaf38e400a73" + integrity sha512-xMvz9IwrXisQ1MH+Tj6lfbQcQSiQy88nTPuQV6OTLBGuV+vIQeVwXeIkQeTKuSpd0GqZvigPdRqxyQCa3blpIg== + "@gitlab/vue-toasted@^1.2.1": version "1.2.1" resolved "https://registry.yarnpkg.com/@gitlab/vue-toasted/-/vue-toasted-1.2.1.tgz#f407b5aa710863e5b7f021f4a1f66160331ab263" -- cgit v1.2.3