diff --git a/services/web/.eslintrc b/services/web/.eslintrc
index 85819a2a86..239ea7d1c3 100644
--- a/services/web/.eslintrc
+++ b/services/web/.eslintrc
@@ -226,7 +226,32 @@
// Require .jsx or .tsx file extension when using JSX
"react/jsx-filename-extension": ["error", {
"extensions": [".jsx", ".tsx"]
- }]
+ }],
+ "no-restricted-syntax": [
+ "error",
+ // Begin: Make sure angular can withstand minification
+ {
+ "selector": "CallExpression[callee.object.name='App'][callee.property.name=/run|directive|config|controller/] > :function[params.length > 0]",
+ "message": "Wrap the function in an array with the parameter names, to withstand minifcation. E.g. App.controller('MyController', ['param1', function(param1) {}]"
+ },
+ {
+ "selector": "CallExpression[callee.object.name='App'][callee.property.name=/run|directive|config|controller/] > ArrayExpression > ArrowFunctionExpression",
+ "message": "Use standard function syntax instead of arrow function syntax in angular components. E.g. function(param1) {}"
+ },
+ {
+ "selector": "CallExpression[callee.object.name='App'][callee.property.name=/run|directive|config|controller/] > ArrowFunctionExpression",
+ "message": "Use standard function syntax instead of arrow function syntax in angular components. E.g. function(param1) {}"
+ },
+ {
+ "selector": "CallExpression[callee.object.name='App'][callee.property.name=/run|directive|config|controller/] > ArrayExpression > :not(:function, Identifier):last-child",
+ "message": "Last element of the array must be a function. E.g ['param1', function(param1) {}]"
+ },
+ {
+ "selector": "CallExpression[callee.object.name='App'][callee.property.name=/run|directive|config|controller/] > ArrayExpression[elements.length=0]",
+ "message": "Array must not be empty. Add parameters and a function. E.g ['param1', function(param1) {}]"
+ }
+ // End: Make sure angular can withstand minification
+ ]
}
},
// React + TypeScript-specific rules
diff --git a/services/web/frontend/js/directives/asyncForm.js b/services/web/frontend/js/directives/asyncForm.js
index 626e63a5be..9348b756df 100644
--- a/services/web/frontend/js/directives/asyncForm.js
+++ b/services/web/frontend/js/directives/asyncForm.js
@@ -4,177 +4,180 @@ App.directive('asyncForm', [
'$http',
'validateCaptcha',
'validateCaptchaV3',
- ($http, validateCaptcha, validateCaptchaV3) => ({
- controller: [
- '$scope',
- '$location',
- function ($scope, $location) {
- this.getEmail = () => $scope.email
- this.getEmailFromQuery = () =>
- $location.search().email || $location.search().new_email
- return this
- },
- ],
- link(scope, element, attrs, ctrl) {
- let response
- const formName = attrs.asyncForm
+ function ($http, validateCaptcha, validateCaptchaV3) {
+ return {
+ controller: [
+ '$scope',
+ '$location',
+ function ($scope, $location) {
+ this.getEmail = () => $scope.email
+ this.getEmailFromQuery = () =>
+ $location.search().email || $location.search().new_email
+ return this
+ },
+ ],
+ link(scope, element, attrs, ctrl) {
+ let response
+ const formName = attrs.asyncForm
- scope[attrs.name].response = response = {}
- scope[attrs.name].inflight = false
- scope.email =
- scope.email ||
- scope.usersEmail ||
- ctrl.getEmailFromQuery() ||
- attrs.newEmail
+ scope[attrs.name].response = response = {}
+ scope[attrs.name].inflight = false
+ scope.email =
+ scope.email ||
+ scope.usersEmail ||
+ ctrl.getEmailFromQuery() ||
+ attrs.newEmail
- const validateCaptchaIfEnabled = function (callback) {
- scope.$applyAsync(() => {
- scope[attrs.name].inflight = true
- })
+ const validateCaptchaIfEnabled = function (callback) {
+ scope.$applyAsync(() => {
+ scope[attrs.name].inflight = true
+ })
- if (attrs.captchaActionName) {
- validateCaptchaV3(attrs.captchaActionName)
- }
- if (attrs.captcha != null) {
- validateCaptcha(callback)
- } else {
- callback()
- }
- }
-
- const _submitRequest = function (grecaptchaResponse) {
- const formData = {}
- for (const data of Array.from(element.serializeArray())) {
- formData[data.name] = data.value
+ if (attrs.captchaActionName) {
+ validateCaptchaV3(attrs.captchaActionName)
+ }
+ if (attrs.captcha != null) {
+ validateCaptcha(callback)
+ } else {
+ callback()
+ }
}
- if (grecaptchaResponse) {
- formData['g-recaptcha-response'] = grecaptchaResponse
- }
+ const _submitRequest = function (grecaptchaResponse) {
+ const formData = {}
+ for (const data of Array.from(element.serializeArray())) {
+ formData[data.name] = data.value
+ }
- // clear the response object which may be referenced downstream
- Object.keys(response).forEach(field => delete response[field])
+ if (grecaptchaResponse) {
+ formData['g-recaptcha-response'] = grecaptchaResponse
+ }
- // for asyncForm prevent automatic redirect to /login if
- // authentication fails, we will handle it ourselves
- const httpRequestFn = _httpRequestFn(element.attr('method'))
- return httpRequestFn(element.attr('action'), formData, {
- disableAutoLoginRedirect: true,
- })
- .then(function (httpResponse) {
- const { data, headers } = httpResponse
- scope[attrs.name].inflight = false
- response.success = true
- response.error = false
+ // clear the response object which may be referenced downstream
+ Object.keys(response).forEach(field => delete response[field])
- const onSuccessHandler = scope[attrs.onSuccess]
- if (onSuccessHandler) {
- onSuccessHandler(httpResponse)
- return
- }
+ // for asyncForm prevent automatic redirect to /login if
+ // authentication fails, we will handle it ourselves
+ const httpRequestFn = _httpRequestFn(element.attr('method'))
+ return httpRequestFn(element.attr('action'), formData, {
+ disableAutoLoginRedirect: true,
+ })
+ .then(function (httpResponse) {
+ const { data, headers } = httpResponse
+ scope[attrs.name].inflight = false
+ response.success = true
+ response.error = false
- if (data.redir) {
- ga('send', 'event', formName, 'success')
- return (window.location = data.redir)
- } else if (data.message) {
- response.message = data.message
+ const onSuccessHandler = scope[attrs.onSuccess]
+ if (onSuccessHandler) {
+ onSuccessHandler(httpResponse)
+ return
+ }
- if (data.message.type === 'error') {
- response.success = false
- response.error = true
- return ga('send', 'event', formName, 'failure', data.message)
+ if (data.redir) {
+ ga('send', 'event', formName, 'success')
+ return (window.location = data.redir)
+ } else if (data.message) {
+ response.message = data.message
+
+ if (data.message.type === 'error') {
+ response.success = false
+ response.error = true
+ return ga('send', 'event', formName, 'failure', data.message)
+ } else {
+ return ga('send', 'event', formName, 'success')
+ }
+ } else if (scope.$eval(attrs.asyncFormDownloadResponse)) {
+ const blob = new Blob([data], {
+ type: headers('Content-Type'),
+ })
+ location.href = URL.createObjectURL(blob) // Trigger file save
+ }
+ })
+ .catch(function (httpResponse) {
+ const { data, status } = httpResponse
+ scope[attrs.name].inflight = false
+ response.success = false
+ response.error = true
+ response.status = status
+ response.data = data
+
+ const onErrorHandler = scope[attrs.onError]
+ if (onErrorHandler) {
+ onErrorHandler(httpResponse)
+ return
+ }
+
+ let responseMessage
+ if (data.message && data.message.text) {
+ responseMessage = data.message.text
} else {
- return ga('send', 'event', formName, 'success')
+ responseMessage = data.message
}
- } else if (scope.$eval(attrs.asyncFormDownloadResponse)) {
- const blob = new Blob([data], {
- type: headers('Content-Type'),
- })
- location.href = URL.createObjectURL(blob) // Trigger file save
- }
- })
- .catch(function (httpResponse) {
- const { data, status } = httpResponse
- scope[attrs.name].inflight = false
- response.success = false
- response.error = true
- response.status = status
- response.data = data
- const onErrorHandler = scope[attrs.onError]
- if (onErrorHandler) {
- onErrorHandler(httpResponse)
- return
- }
-
- let responseMessage
- if (data.message && data.message.text) {
- responseMessage = data.message.text
- } else {
- responseMessage = data.message
- }
-
- if (status === 400) {
- // Bad Request
- response.message = {
- text:
- responseMessage ||
- 'Invalid Request. Please correct the data and try again.',
- type: 'error',
+ if (status === 400) {
+ // Bad Request
+ response.message = {
+ text:
+ responseMessage ||
+ 'Invalid Request. Please correct the data and try again.',
+ type: 'error',
+ }
+ } else if (status === 403) {
+ // Forbidden
+ response.message = {
+ text:
+ responseMessage ||
+ 'Session error. Please check you have cookies enabled. If the problem persists, try clearing your cache and cookies.',
+ type: 'error',
+ }
+ } else if (status === 429) {
+ response.message = {
+ text:
+ responseMessage ||
+ 'Too many attempts. Please wait for a while and try again.',
+ type: 'error',
+ }
+ } else {
+ response.message = {
+ text:
+ responseMessage ||
+ 'Something went wrong talking to the server :(. Please try again.',
+ type: 'error',
+ }
}
- } else if (status === 403) {
- // Forbidden
- response.message = {
- text:
- responseMessage ||
- 'Session error. Please check you have cookies enabled. If the problem persists, try clearing your cache and cookies.',
- type: 'error',
- }
- } else if (status === 429) {
- response.message = {
- text:
- responseMessage ||
- 'Too many attempts. Please wait for a while and try again.',
- type: 'error',
- }
- } else {
- response.message = {
- text:
- responseMessage ||
- 'Something went wrong talking to the server :(. Please try again.',
- type: 'error',
- }
- }
- ga('send', 'event', formName, 'failure', data.message)
- })
- }
-
- const submit = () =>
- validateCaptchaIfEnabled(response => _submitRequest(response))
-
- const _httpRequestFn = (method = 'post') => {
- const $HTTP_FNS = {
- post: $http.post,
- get: $http.get,
+ ga('send', 'event', formName, 'failure', data.message)
+ })
}
- return $HTTP_FNS[method.toLowerCase()]
- }
- element.on('submit', function (e) {
- e.preventDefault()
- submit()
- })
+ const submit = () =>
+ validateCaptchaIfEnabled(response => _submitRequest(response))
- if (attrs.autoSubmit) {
- submit()
- }
- },
- }),
+ const _httpRequestFn = (method = 'post') => {
+ const $HTTP_FNS = {
+ post: $http.post,
+ get: $http.get,
+ }
+ return $HTTP_FNS[method.toLowerCase()]
+ }
+
+ element.on('submit', function (e) {
+ e.preventDefault()
+ submit()
+ })
+
+ if (attrs.autoSubmit) {
+ submit()
+ }
+ },
+ }
+ },
])
-App.directive('formMessages', () => ({
- restrict: 'E',
- template: `\
+App.directive('formMessages', function () {
+ return {
+ restrict: 'E',
+ template: `\
\
`)(customTogglerScope)
- element.append(customTogglerEl)
- }
-
- function onPaneOpen(pane) {
- if (!hasCustomToggler || pane !== customTogglerPane) {
- return
+ element.append(customTogglerEl)
}
- return customTogglerEl
- .scope()
- .$applyAsync(() => (customTogglerEl.scope().isOpen = true))
- }
- function onPaneClose(pane) {
- if (!hasCustomToggler || pane !== customTogglerPane) {
- return
+ function onPaneOpen(pane) {
+ if (!hasCustomToggler || pane !== customTogglerPane) {
+ return
+ }
+ return customTogglerEl
+ .scope()
+ .$applyAsync(() => (customTogglerEl.scope().isOpen = true))
}
- return customTogglerEl
- .scope()
- .$applyAsync(() => (customTogglerEl.scope().isOpen = false))
- }
- // Ensure editor resizes after loading. This is to handle the case where
- // the window has been resized while the editor is loading
- scope.$on('editor:loaded', () => {
- ide.$timeout(() => layout.resizeAll())
- })
+ function onPaneClose(pane) {
+ if (!hasCustomToggler || pane !== customTogglerPane) {
+ return
+ }
+ return customTogglerEl
+ .scope()
+ .$applyAsync(() => (customTogglerEl.scope().isOpen = false))
+ }
- // Save state when exiting
- $(window).unload(() => {
- // Save only the state properties for the current layout, ignoring sublayouts inside it.
- // If we save sublayouts state (`children`), the layout library will use it when
- // initializing. This raises errors when the sublayout elements aren't available (due to
- // being loaded at init or just not existing for the current project/user).
- const stateToSave = _.mapValues(layout.readState(), pane =>
- _.omit(pane, 'children')
- )
- ide.localStorage(`layout.${name}`, stateToSave)
- })
+ // Ensure editor resizes after loading. This is to handle the case where
+ // the window has been resized while the editor is loading
+ scope.$on('editor:loaded', () => {
+ ide.$timeout(() => layout.resizeAll())
+ })
- if (attrs.openEast != null) {
- scope.$watch(attrs.openEast, function (value, oldValue) {
- if (value != null && value !== oldValue) {
- if (value) {
- layout.open('east')
- } else {
- layout.close('east')
+ // Save state when exiting
+ $(window).unload(() => {
+ // Save only the state properties for the current layout, ignoring sublayouts inside it.
+ // If we save sublayouts state (`children`), the layout library will use it when
+ // initializing. This raises errors when the sublayout elements aren't available (due to
+ // being loaded at init or just not existing for the current project/user).
+ const stateToSave = _.mapValues(layout.readState(), pane =>
+ _.omit(pane, 'children')
+ )
+ ide.localStorage(`layout.${name}`, stateToSave)
+ })
+
+ if (attrs.openEast != null) {
+ scope.$watch(attrs.openEast, function (value, oldValue) {
+ if (value != null && value !== oldValue) {
+ if (value) {
+ layout.open('east')
+ } else {
+ layout.close('east')
+ }
+ if (hasCustomToggler && customTogglerPane === 'east') {
+ repositionCustomToggler()
+ customTogglerEl.scope().$applyAsync(function () {
+ customTogglerEl.scope().isOpen = value
+ })
+ }
}
- if (hasCustomToggler && customTogglerPane === 'east') {
- repositionCustomToggler()
- customTogglerEl.scope().$applyAsync(function () {
- customTogglerEl.scope().isOpen = value
+ return setTimeout(() => scope.$digest(), 0)
+ })
+ }
+
+ if (attrs.allowOverflowOn != null) {
+ const overflowPane = scope.$eval(attrs.allowOverflowOn)
+ const overflowPaneEl = layout.panes[overflowPane]
+ // Set the panel as overflowing (gives it higher z-index and sets overflow rules)
+ layout.allowOverflow(overflowPane)
+ // Read the given z-index value and increment it, so that it's higher than synctex controls.
+ const overflowPaneZVal = overflowPaneEl.zIndex()
+ overflowPaneEl.css('z-index', overflowPaneZVal + 1)
+ }
+
+ resetOpenStates()
+ onInternalResize()
+
+ if (attrs.layoutDisabled != null) {
+ return scope.$watch(attrs.layoutDisabled, function (value) {
+ if (value) {
+ layout.hide('east')
+ } else {
+ layout.show('east')
+ }
+ if (hasCustomToggler) {
+ return customTogglerEl.scope().$applyAsync(function () {
+ customTogglerEl.scope().isOpen = !value
+ return (customTogglerEl.scope().isVisible = !value)
})
}
- }
- return setTimeout(() => scope.$digest(), 0)
- })
- }
+ })
+ }
+ },
- if (attrs.allowOverflowOn != null) {
- const overflowPane = scope.$eval(attrs.allowOverflowOn)
- const overflowPaneEl = layout.panes[overflowPane]
- // Set the panel as overflowing (gives it higher z-index and sets overflow rules)
- layout.allowOverflow(overflowPane)
- // Read the given z-index value and increment it, so that it's higher than synctex controls.
- const overflowPaneZVal = overflowPaneEl.zIndex()
- overflowPaneEl.css('z-index', overflowPaneZVal + 1)
- }
-
- resetOpenStates()
- onInternalResize()
-
- if (attrs.layoutDisabled != null) {
- return scope.$watch(attrs.layoutDisabled, function (value) {
- if (value) {
- layout.hide('east')
- } else {
- layout.show('east')
- }
- if (hasCustomToggler) {
- return customTogglerEl.scope().$applyAsync(function () {
- customTogglerEl.scope().isOpen = !value
- return (customTogglerEl.scope().isVisible = !value)
- })
- }
- })
- }
- },
-
- post(scope, element, attrs) {
- const name = attrs.layout
- const state = element.layout().readState()
- return scope.$broadcast(`layout:${name}:linked`, state)
- },
- }
- },
- }),
+ post(scope, element, attrs) {
+ const name = attrs.layout
+ const state = element.layout().readState()
+ return scope.$broadcast(`layout:${name}:linked`, state)
+ },
+ }
+ },
+ }
+ },
])
diff --git a/services/web/frontend/js/ide/directives/validFile.js b/services/web/frontend/js/ide/directives/validFile.js
index 3cc131476c..339bc99d67 100644
--- a/services/web/frontend/js/ide/directives/validFile.js
+++ b/services/web/frontend/js/ide/directives/validFile.js
@@ -12,10 +12,12 @@
import App from '../../base'
import SafePath from './SafePath'
-export default App.directive('validFile', () => ({
- require: 'ngModel',
- link(scope, element, attrs, ngModelCtrl) {
- return (ngModelCtrl.$validators.validFile = filename =>
- SafePath.isCleanFilename(filename))
- },
-}))
+export default App.directive('validFile', function () {
+ return {
+ require: 'ngModel',
+ link(scope, element, attrs, ngModelCtrl) {
+ return (ngModelCtrl.$validators.validFile = filename =>
+ SafePath.isCleanFilename(filename))
+ },
+ }
+})
diff --git a/services/web/frontend/js/ide/directives/verticalResizablePanes.js b/services/web/frontend/js/ide/directives/verticalResizablePanes.js
index cc91746a20..b6fe36867d 100644
--- a/services/web/frontend/js/ide/directives/verticalResizablePanes.js
+++ b/services/web/frontend/js/ide/directives/verticalResizablePanes.js
@@ -3,131 +3,137 @@ import App from '../../base'
export default App.directive('verticalResizablePanes', [
'localStorage',
'ide',
- (localStorage, ide) => ({
- restrict: 'A',
- link(scope, element, attrs) {
- const name = attrs.verticalResizablePanes
- const minSize = scope.$eval(attrs.verticalResizablePanesMinSize)
- const maxSize = scope.$eval(attrs.verticalResizablePanesMaxSize)
- const defaultSize = scope.$eval(attrs.verticalResizablePanesDefaultSize)
- let storedSize = null
- let manualResizeIncoming = false
+ function (localStorage, ide) {
+ return {
+ restrict: 'A',
+ link(scope, element, attrs) {
+ const name = attrs.verticalResizablePanes
+ const minSize = scope.$eval(attrs.verticalResizablePanesMinSize)
+ const maxSize = scope.$eval(attrs.verticalResizablePanesMaxSize)
+ const defaultSize = scope.$eval(attrs.verticalResizablePanesDefaultSize)
+ let storedSize = null
+ let manualResizeIncoming = false
- if (name) {
- const storageKey = `vertical-resizable:${name}:south-size`
- storedSize = localStorage(storageKey)
- $(window).unload(() => {
- if (storedSize) {
- localStorage(storageKey, storedSize)
- }
- })
- }
-
- const layoutOptions = {
- center: {
- paneSelector: '[vertical-resizable-top]',
- paneClass: 'vertical-resizable-top',
- size: 'auto',
- },
- south: {
- paneSelector: '[vertical-resizable-bottom]',
- paneClass: 'vertical-resizable-bottom',
- resizerClass: 'vertical-resizable-resizer',
- resizerCursor: 'ns-resize',
- size: 'auto',
- resizable: true,
- closable: false,
- slidable: false,
- spacing_open: 6,
- spacing_closed: 6,
- maxSize: '75%',
- },
- }
-
- const toggledExternally = attrs.verticalResizablePanesToggledExternallyOn
- const hiddenExternally = attrs.verticalResizablePanesHiddenExternallyOn
- const hiddenInitially = attrs.verticalResizablePanesHiddenInitially
- const resizeOn = attrs.verticalResizablePanesResizeOn
- const resizerDisabledClass = `${layoutOptions.south.resizerClass}-disabled`
-
- function enableResizer() {
- if (layoutHandle.resizers && layoutHandle.resizers.south) {
- layoutHandle.resizers.south.removeClass(resizerDisabledClass)
- }
- }
-
- function disableResizer() {
- if (layoutHandle.resizers && layoutHandle.resizers.south) {
- layoutHandle.resizers.south.addClass(resizerDisabledClass)
- }
- }
-
- function handleDragEnd() {
- manualResizeIncoming = true
- }
-
- function handleResize(paneName, paneEl, paneState) {
- if (manualResizeIncoming) {
- storedSize = paneState.size
- }
- manualResizeIncoming = false
- }
-
- if (toggledExternally) {
- scope.$on(toggledExternally, (e, open) => {
- if (open) {
- enableResizer()
- layoutHandle.sizePane('south', storedSize ?? defaultSize ?? 'auto')
- } else {
- disableResizer()
- layoutHandle.sizePane('south', 'auto')
- }
- })
- }
-
- if (hiddenExternally) {
- ide.$scope.$on(hiddenExternally, (e, open) => {
- if (open) {
- layoutHandle.show('south')
- } else {
- layoutHandle.hide('south')
- }
- })
- }
-
- if (resizeOn) {
- ide.$scope.$on(resizeOn, () => {
- ide.$timeout(() => {
- layoutHandle.resizeAll()
+ if (name) {
+ const storageKey = `vertical-resizable:${name}:south-size`
+ storedSize = localStorage(storageKey)
+ $(window).unload(() => {
+ if (storedSize) {
+ localStorage(storageKey, storedSize)
+ }
})
- })
- }
+ }
- if (maxSize) {
- layoutOptions.south.maxSize = maxSize
- }
+ const layoutOptions = {
+ center: {
+ paneSelector: '[vertical-resizable-top]',
+ paneClass: 'vertical-resizable-top',
+ size: 'auto',
+ },
+ south: {
+ paneSelector: '[vertical-resizable-bottom]',
+ paneClass: 'vertical-resizable-bottom',
+ resizerClass: 'vertical-resizable-resizer',
+ resizerCursor: 'ns-resize',
+ size: 'auto',
+ resizable: true,
+ closable: false,
+ slidable: false,
+ spacing_open: 6,
+ spacing_closed: 6,
+ maxSize: '75%',
+ },
+ }
- if (minSize) {
- layoutOptions.south.minSize = minSize
- }
+ const toggledExternally =
+ attrs.verticalResizablePanesToggledExternallyOn
+ const hiddenExternally = attrs.verticalResizablePanesHiddenExternallyOn
+ const hiddenInitially = attrs.verticalResizablePanesHiddenInitially
+ const resizeOn = attrs.verticalResizablePanesResizeOn
+ const resizerDisabledClass = `${layoutOptions.south.resizerClass}-disabled`
- if (defaultSize) {
- layoutOptions.south.size = defaultSize
- }
+ function enableResizer() {
+ if (layoutHandle.resizers && layoutHandle.resizers.south) {
+ layoutHandle.resizers.south.removeClass(resizerDisabledClass)
+ }
+ }
- // The `drag` event fires only when the user manually resizes the panes; the `resize` event fires even when
- // the layout library internally resizes itself. In order to get explicit user-initiated resizes, we need to
- // listen to `drag` events. However, when the `drag` event fires, the panes aren't yet finished sizing so we
- // get the pane size *before* the resize happens. We do get the correct size in the next `resize` event.
- // The solution to work around this is to set up a flag in `drag` events which tells the next `resize` event
- // that it was user-initiated (therefore, storing the value).
- layoutOptions.south.ondrag_end = handleDragEnd
- layoutOptions.south.onresize = handleResize
+ function disableResizer() {
+ if (layoutHandle.resizers && layoutHandle.resizers.south) {
+ layoutHandle.resizers.south.addClass(resizerDisabledClass)
+ }
+ }
- const layoutHandle = element.layout(layoutOptions)
- if (hiddenInitially === 'true') {
- layoutHandle.hide('south')
- }
- },
- }),
+ function handleDragEnd() {
+ manualResizeIncoming = true
+ }
+
+ function handleResize(paneName, paneEl, paneState) {
+ if (manualResizeIncoming) {
+ storedSize = paneState.size
+ }
+ manualResizeIncoming = false
+ }
+
+ if (toggledExternally) {
+ scope.$on(toggledExternally, (e, open) => {
+ if (open) {
+ enableResizer()
+ layoutHandle.sizePane(
+ 'south',
+ storedSize ?? defaultSize ?? 'auto'
+ )
+ } else {
+ disableResizer()
+ layoutHandle.sizePane('south', 'auto')
+ }
+ })
+ }
+
+ if (hiddenExternally) {
+ ide.$scope.$on(hiddenExternally, (e, open) => {
+ if (open) {
+ layoutHandle.show('south')
+ } else {
+ layoutHandle.hide('south')
+ }
+ })
+ }
+
+ if (resizeOn) {
+ ide.$scope.$on(resizeOn, () => {
+ ide.$timeout(() => {
+ layoutHandle.resizeAll()
+ })
+ })
+ }
+
+ if (maxSize) {
+ layoutOptions.south.maxSize = maxSize
+ }
+
+ if (minSize) {
+ layoutOptions.south.minSize = minSize
+ }
+
+ if (defaultSize) {
+ layoutOptions.south.size = defaultSize
+ }
+
+ // The `drag` event fires only when the user manually resizes the panes; the `resize` event fires even when
+ // the layout library internally resizes itself. In order to get explicit user-initiated resizes, we need to
+ // listen to `drag` events. However, when the `drag` event fires, the panes aren't yet finished sizing so we
+ // get the pane size *before* the resize happens. We do get the correct size in the next `resize` event.
+ // The solution to work around this is to set up a flag in `drag` events which tells the next `resize` event
+ // that it was user-initiated (therefore, storing the value).
+ layoutOptions.south.ondrag_end = handleDragEnd
+ layoutOptions.south.onresize = handleResize
+
+ const layoutHandle = element.layout(layoutOptions)
+ if (hiddenInitially === 'true') {
+ layoutHandle.hide('south')
+ }
+ },
+ }
+ },
])
diff --git a/services/web/frontend/js/ide/editor/directives/formattingButtons.js b/services/web/frontend/js/ide/editor/directives/formattingButtons.js
index 0508f8280a..71bbc32991 100644
--- a/services/web/frontend/js/ide/editor/directives/formattingButtons.js
+++ b/services/web/frontend/js/ide/editor/directives/formattingButtons.js
@@ -1,17 +1,19 @@
import App from '../../../base'
-export default App.directive('formattingButtons', () => ({
- scope: {
- buttons: '=',
- opening: '=',
- isFullscreenEditor: '=',
- },
+export default App.directive('formattingButtons', function () {
+ return {
+ scope: {
+ buttons: '=',
+ opening: '=',
+ isFullscreenEditor: '=',
+ },
- link(scope, element, attrs) {
- scope.showMore = false
- scope.shownButtons = scope.buttons
- scope.overflowedButtons = []
- },
+ link(scope, element, attrs) {
+ scope.showMore = false
+ scope.shownButtons = scope.buttons
+ scope.overflowedButtons = []
+ },
- templateUrl: 'formattingButtonsTpl',
-}))
+ templateUrl: 'formattingButtonsTpl',
+ }
+})
diff --git a/services/web/frontend/js/ide/editor/directives/toggleSwitch.js b/services/web/frontend/js/ide/editor/directives/toggleSwitch.js
index 2dbfc46878..ee4335d74b 100644
--- a/services/web/frontend/js/ide/editor/directives/toggleSwitch.js
+++ b/services/web/frontend/js/ide/editor/directives/toggleSwitch.js
@@ -1,14 +1,15 @@
import App from '../../../base'
-export default App.directive('toggleSwitch', () => ({
- restrict: 'E',
- scope: {
- description: '@',
- labelFalse: '@',
- labelTrue: '@',
- ngModel: '=',
- },
- template: `\
+export default App.directive('toggleSwitch', function () {
+ return {
+ restrict: 'E',
+ scope: {
+ description: '@',
+ labelFalse: '@',
+ labelTrue: '@',
+ ngModel: '=',
+ },
+ template: `\
\
`,
-}))
+ }
+})
diff --git a/services/web/frontend/js/ide/history/directives/historyDraggableBoundary.js b/services/web/frontend/js/ide/history/directives/historyDraggableBoundary.js
index 34aa84bba6..9a115108d6 100644
--- a/services/web/frontend/js/ide/history/directives/historyDraggableBoundary.js
+++ b/services/web/frontend/js/ide/history/directives/historyDraggableBoundary.js
@@ -1,33 +1,35 @@
import App from '../../../base'
-export default App.directive('historyDraggableBoundary', () => ({
- scope: {
- historyDraggableBoundary: '@',
- historyDraggableBoundaryOnDragStart: '&',
- historyDraggableBoundaryOnDragStop: '&',
- },
- restrict: 'A',
- link(scope, element, attrs) {
- element.data('selectionBoundary', {
- boundary: scope.historyDraggableBoundary,
- })
- element.draggable({
- axis: 'y',
- opacity: false,
- helper: 'clone',
- revert: true,
- scroll: true,
- cursor: 'row-resize',
- start(e, ui) {
- ui.helper.data('wasProperlyDropped', false)
- scope.historyDraggableBoundaryOnDragStart()
- },
- stop(e, ui) {
- scope.historyDraggableBoundaryOnDragStop({
- isValidDrop: ui.helper.data('wasProperlyDropped'),
- boundary: scope.historyDraggableBoundary,
- })
- },
- })
- },
-}))
+export default App.directive('historyDraggableBoundary', function () {
+ return {
+ scope: {
+ historyDraggableBoundary: '@',
+ historyDraggableBoundaryOnDragStart: '&',
+ historyDraggableBoundaryOnDragStop: '&',
+ },
+ restrict: 'A',
+ link(scope, element, attrs) {
+ element.data('selectionBoundary', {
+ boundary: scope.historyDraggableBoundary,
+ })
+ element.draggable({
+ axis: 'y',
+ opacity: false,
+ helper: 'clone',
+ revert: true,
+ scroll: true,
+ cursor: 'row-resize',
+ start(e, ui) {
+ ui.helper.data('wasProperlyDropped', false)
+ scope.historyDraggableBoundaryOnDragStart()
+ },
+ stop(e, ui) {
+ scope.historyDraggableBoundaryOnDragStop({
+ isValidDrop: ui.helper.data('wasProperlyDropped'),
+ boundary: scope.historyDraggableBoundary,
+ })
+ },
+ })
+ },
+ }
+})
diff --git a/services/web/frontend/js/ide/history/directives/historyDroppableArea.js b/services/web/frontend/js/ide/history/directives/historyDroppableArea.js
index 8f0b44ab38..082c1b553e 100644
--- a/services/web/frontend/js/ide/history/directives/historyDroppableArea.js
+++ b/services/web/frontend/js/ide/history/directives/historyDroppableArea.js
@@ -1,24 +1,28 @@
import App from '../../../base'
-export default App.directive('historyDroppableArea', () => ({
- scope: {
- historyDroppableAreaOnDrop: '&',
- historyDroppableAreaOnOver: '&',
- historyDroppableAreaOnOut: '&',
- },
- restrict: 'A',
- link(scope, element, attrs) {
- element.droppable({
- accept: e => '.history-entry-toV-handle, .history-entry-fromV-handle',
- drop: (e, ui) => {
- const draggedBoundary = ui.draggable.data('selectionBoundary').boundary
- ui.helper.data('wasProperlyDropped', true)
- scope.historyDroppableAreaOnDrop({ boundary: draggedBoundary })
- },
- over: (e, ui) => {
- const draggedBoundary = ui.draggable.data('selectionBoundary').boundary
- scope.historyDroppableAreaOnOver({ boundary: draggedBoundary })
- },
- })
- },
-}))
+export default App.directive('historyDroppableArea', function () {
+ return {
+ scope: {
+ historyDroppableAreaOnDrop: '&',
+ historyDroppableAreaOnOver: '&',
+ historyDroppableAreaOnOut: '&',
+ },
+ restrict: 'A',
+ link(scope, element, attrs) {
+ element.droppable({
+ accept: e => '.history-entry-toV-handle, .history-entry-fromV-handle',
+ drop: (e, ui) => {
+ const draggedBoundary =
+ ui.draggable.data('selectionBoundary').boundary
+ ui.helper.data('wasProperlyDropped', true)
+ scope.historyDroppableAreaOnDrop({ boundary: draggedBoundary })
+ },
+ over: (e, ui) => {
+ const draggedBoundary =
+ ui.draggable.data('selectionBoundary').boundary
+ scope.historyDroppableAreaOnOver({ boundary: draggedBoundary })
+ },
+ })
+ },
+ }
+})
diff --git a/services/web/frontend/js/ide/history/directives/infiniteScroll.js b/services/web/frontend/js/ide/history/directives/infiniteScroll.js
index b559b28990..dbc5cc704c 100644
--- a/services/web/frontend/js/ide/history/directives/infiniteScroll.js
+++ b/services/web/frontend/js/ide/history/directives/infiniteScroll.js
@@ -11,43 +11,45 @@
*/
import App from '../../../base'
-export default App.directive('infiniteScroll', () => ({
- link(scope, element, attrs, ctrl) {
- const innerElement = element.find('.infinite-scroll-inner')
- element.css({ 'overflow-y': 'auto' })
+export default App.directive('infiniteScroll', function () {
+ return {
+ link(scope, element, attrs, ctrl) {
+ const innerElement = element.find('.infinite-scroll-inner')
+ element.css({ 'overflow-y': 'auto' })
- const atEndOfListView = function () {
- if (attrs.infiniteScrollUpwards != null) {
- return atTopOfListView()
- } else {
- return atBottomOfListView()
+ const atEndOfListView = function () {
+ if (attrs.infiniteScrollUpwards != null) {
+ return atTopOfListView()
+ } else {
+ return atBottomOfListView()
+ }
}
- }
- const atTopOfListView = () => element.scrollTop() < 30
+ const atTopOfListView = () => element.scrollTop() < 30
- const atBottomOfListView = () =>
- element.scrollTop() + element.height() >= innerElement.height() - 30
+ const atBottomOfListView = () =>
+ element.scrollTop() + element.height() >= innerElement.height() - 30
- const listShorterThanContainer = () =>
- element.height() > innerElement.height()
+ const listShorterThanContainer = () =>
+ element.height() > innerElement.height()
- function loadUntilFull() {
- if (
- (listShorterThanContainer() || atEndOfListView()) &&
- !scope.$eval(attrs.infiniteScrollDisabled)
- ) {
- const promise = scope.$eval(attrs.infiniteScroll)
- return promise.then(() => setTimeout(() => loadUntilFull(), 0))
+ function loadUntilFull() {
+ if (
+ (listShorterThanContainer() || atEndOfListView()) &&
+ !scope.$eval(attrs.infiniteScrollDisabled)
+ ) {
+ const promise = scope.$eval(attrs.infiniteScroll)
+ return promise.then(() => setTimeout(() => loadUntilFull(), 0))
+ }
}
- }
- element.on('scroll', event => loadUntilFull())
+ element.on('scroll', event => loadUntilFull())
- return scope.$watch(attrs.infiniteScrollInitialize, function (value) {
- if (value) {
- return loadUntilFull()
- }
- })
- },
-}))
+ return scope.$watch(attrs.infiniteScrollInitialize, function (value) {
+ if (value) {
+ return loadUntilFull()
+ }
+ })
+ },
+ }
+})
diff --git a/services/web/frontend/js/ide/review-panel/directives/addCommentEntry.js b/services/web/frontend/js/ide/review-panel/directives/addCommentEntry.js
index 4255e630f1..d74258e613 100644
--- a/services/web/frontend/js/ide/review-panel/directives/addCommentEntry.js
+++ b/services/web/frontend/js/ide/review-panel/directives/addCommentEntry.js
@@ -1,60 +1,62 @@
import App from '../../../base'
let content = ''
-App.directive('addCommentEntry', () => ({
- restrict: 'E',
- templateUrl: 'addCommentEntryTemplate',
- scope: {
- onStartNew: '&',
- onSubmit: '&',
- onCancel: '&',
- },
- link(scope, element, attrs) {
- scope.state = {
- isAdding: false,
- content,
- }
+App.directive('addCommentEntry', function () {
+ return {
+ restrict: 'E',
+ templateUrl: 'addCommentEntryTemplate',
+ scope: {
+ onStartNew: '&',
+ onSubmit: '&',
+ onCancel: '&',
+ },
+ link(scope, element, attrs) {
+ scope.state = {
+ isAdding: false,
+ content,
+ }
- scope.$on('comment:start_adding', () => scope.startNewComment())
- scope.$on('$destroy', function () {
- content = scope.state.content
- })
+ scope.$on('comment:start_adding', () => scope.startNewComment())
+ scope.$on('$destroy', function () {
+ content = scope.state.content
+ })
- scope.startNewComment = function () {
- scope.state.isAdding = true
- scope.onStartNew()
- setTimeout(() => scope.$broadcast('comment:new:open'))
- }
+ scope.startNewComment = function () {
+ scope.state.isAdding = true
+ scope.onStartNew()
+ setTimeout(() => scope.$broadcast('comment:new:open'))
+ }
- scope.cancelNewComment = function () {
- scope.state.isAdding = false
- scope.state.content = ''
- scope.onCancel()
- }
+ scope.cancelNewComment = function () {
+ scope.state.isAdding = false
+ scope.state.content = ''
+ scope.onCancel()
+ }
- const ignoreKeysInTextAreas = ['PageDown', 'PageUp']
+ const ignoreKeysInTextAreas = ['PageDown', 'PageUp']
- scope.handleCommentKeyDown = function (ev) {
- if (ignoreKeysInTextAreas.includes(ev.key)) {
- if (ev.target.closest('textarea')) {
+ scope.handleCommentKeyDown = function (ev) {
+ if (ignoreKeysInTextAreas.includes(ev.key)) {
+ if (ev.target.closest('textarea')) {
+ ev.preventDefault()
+ }
+ }
+ }
+
+ scope.handleCommentKeyPress = function (ev) {
+ if (ev.keyCode === 13 && !ev.shiftKey && !ev.ctrlKey && !ev.metaKey) {
ev.preventDefault()
+ if (scope.state.content.length > 0) {
+ scope.submitNewComment()
+ }
}
}
- }
- scope.handleCommentKeyPress = function (ev) {
- if (ev.keyCode === 13 && !ev.shiftKey && !ev.ctrlKey && !ev.metaKey) {
- ev.preventDefault()
- if (scope.state.content.length > 0) {
- scope.submitNewComment()
- }
+ scope.submitNewComment = function (event) {
+ scope.onSubmit({ content: scope.state.content })
+ content = scope.state.content
+ scope.state.isAdding = false
+ scope.state.content = ''
}
- }
-
- scope.submitNewComment = function (event) {
- scope.onSubmit({ content: scope.state.content })
- content = scope.state.content
- scope.state.isAdding = false
- scope.state.content = ''
- }
- },
-}))
+ },
+ }
+})
diff --git a/services/web/frontend/js/ide/review-panel/directives/aggregateChangeEntry.js b/services/web/frontend/js/ide/review-panel/directives/aggregateChangeEntry.js
index f2595b7d1b..568b3c7b52 100644
--- a/services/web/frontend/js/ide/review-panel/directives/aggregateChangeEntry.js
+++ b/services/web/frontend/js/ide/review-panel/directives/aggregateChangeEntry.js
@@ -13,60 +13,62 @@ import App from '../../../base'
export default App.directive('aggregateChangeEntry', [
'$timeout',
- $timeout => ({
- restrict: 'E',
- templateUrl: 'aggregateChangeEntryTemplate',
- scope: {
- entry: '=',
- user: '=',
- permissions: '=',
- onAccept: '&',
- onReject: '&',
- onIndicatorClick: '&',
- onMouseEnter: '&',
- onMouseLeave: '&',
- onBodyClick: '&',
- },
- link(scope, element, attrs) {
- scope.contentLimit = 17
- scope.isDeletionCollapsed = true
- scope.isInsertionCollapsed = true
- scope.deletionNeedsCollapsing = false
- scope.insertionNeedsCollapsing = false
+ function ($timeout) {
+ return {
+ restrict: 'E',
+ templateUrl: 'aggregateChangeEntryTemplate',
+ scope: {
+ entry: '=',
+ user: '=',
+ permissions: '=',
+ onAccept: '&',
+ onReject: '&',
+ onIndicatorClick: '&',
+ onMouseEnter: '&',
+ onMouseLeave: '&',
+ onBodyClick: '&',
+ },
+ link(scope, element, attrs) {
+ scope.contentLimit = 17
+ scope.isDeletionCollapsed = true
+ scope.isInsertionCollapsed = true
+ scope.deletionNeedsCollapsing = false
+ scope.insertionNeedsCollapsing = false
- element.on('click', function (e) {
- if (
- $(e.target).is(
- '.rp-entry, .rp-entry-description, .rp-entry-body, .rp-entry-action-icon i'
- )
- ) {
- return scope.onBodyClick()
+ element.on('click', function (e) {
+ if (
+ $(e.target).is(
+ '.rp-entry, .rp-entry-description, .rp-entry-body, .rp-entry-action-icon i'
+ )
+ ) {
+ return scope.onBodyClick()
+ }
+ })
+
+ scope.toggleDeletionCollapse = function () {
+ scope.isDeletionCollapsed = !scope.isDeletionCollapsed
+ return $timeout(() => scope.$emit('review-panel:layout'))
}
- })
- scope.toggleDeletionCollapse = function () {
- scope.isDeletionCollapsed = !scope.isDeletionCollapsed
- return $timeout(() => scope.$emit('review-panel:layout'))
- }
+ scope.toggleInsertionCollapse = function () {
+ scope.isInsertionCollapsed = !scope.isInsertionCollapsed
+ return $timeout(() => scope.$emit('review-panel:layout'))
+ }
- scope.toggleInsertionCollapse = function () {
- scope.isInsertionCollapsed = !scope.isInsertionCollapsed
- return $timeout(() => scope.$emit('review-panel:layout'))
- }
+ scope.$watch(
+ 'entry.metadata.replaced_content.length',
+ deletionContentLength =>
+ (scope.deletionNeedsCollapsing =
+ deletionContentLength > scope.contentLimit)
+ )
- scope.$watch(
- 'entry.metadata.replaced_content.length',
- deletionContentLength =>
- (scope.deletionNeedsCollapsing =
- deletionContentLength > scope.contentLimit)
- )
-
- return scope.$watch(
- 'entry.content.length',
- insertionContentLength =>
- (scope.insertionNeedsCollapsing =
- insertionContentLength > scope.contentLimit)
- )
- },
- }),
+ return scope.$watch(
+ 'entry.content.length',
+ insertionContentLength =>
+ (scope.insertionNeedsCollapsing =
+ insertionContentLength > scope.contentLimit)
+ )
+ },
+ }
+ },
])
diff --git a/services/web/frontend/js/ide/review-panel/directives/bulkActionsEntry.js b/services/web/frontend/js/ide/review-panel/directives/bulkActionsEntry.js
index be154d2d7f..2c2c132e8b 100644
--- a/services/web/frontend/js/ide/review-panel/directives/bulkActionsEntry.js
+++ b/services/web/frontend/js/ide/review-panel/directives/bulkActionsEntry.js
@@ -10,16 +10,18 @@
*/
import App from '../../../base'
-export default App.directive('bulkActionsEntry', () => ({
- restrict: 'E',
- templateUrl: 'bulkActionsEntryTemplate',
- scope: {
- onBulkAccept: '&',
- onBulkReject: '&',
- nEntries: '=',
- },
- link(scope, element, attrs) {
- scope.bulkAccept = () => scope.onBulkAccept()
- return (scope.bulkReject = () => scope.onBulkReject())
- },
-}))
+export default App.directive('bulkActionsEntry', function () {
+ return {
+ restrict: 'E',
+ templateUrl: 'bulkActionsEntryTemplate',
+ scope: {
+ onBulkAccept: '&',
+ onBulkReject: '&',
+ nEntries: '=',
+ },
+ link(scope, element, attrs) {
+ scope.bulkAccept = () => scope.onBulkAccept()
+ return (scope.bulkReject = () => scope.onBulkReject())
+ },
+ }
+})
diff --git a/services/web/frontend/js/ide/review-panel/directives/changeEntry.js b/services/web/frontend/js/ide/review-panel/directives/changeEntry.js
index ee52ede57a..2ee343efe1 100644
--- a/services/web/frontend/js/ide/review-panel/directives/changeEntry.js
+++ b/services/web/frontend/js/ide/review-panel/directives/changeEntry.js
@@ -13,45 +13,47 @@ import App from '../../../base'
export default App.directive('changeEntry', [
'$timeout',
- $timeout => ({
- restrict: 'E',
- templateUrl: 'changeEntryTemplate',
- scope: {
- entry: '=',
- user: '=',
- permissions: '=',
- onAccept: '&',
- onReject: '&',
- onIndicatorClick: '&',
- onMouseEnter: '&',
- onMouseLeave: '&',
- onBodyClick: '&',
- },
- link(scope, element, attrs) {
- scope.contentLimit = 40
- scope.isCollapsed = true
- scope.needsCollapsing = false
+ function ($timeout) {
+ return {
+ restrict: 'E',
+ templateUrl: 'changeEntryTemplate',
+ scope: {
+ entry: '=',
+ user: '=',
+ permissions: '=',
+ onAccept: '&',
+ onReject: '&',
+ onIndicatorClick: '&',
+ onMouseEnter: '&',
+ onMouseLeave: '&',
+ onBodyClick: '&',
+ },
+ link(scope, element, attrs) {
+ scope.contentLimit = 40
+ scope.isCollapsed = true
+ scope.needsCollapsing = false
- element.on('click', function (e) {
- if (
- $(e.target).is(
- '.rp-entry, .rp-entry-description, .rp-entry-body, .rp-entry-action-icon i'
- )
- ) {
- return scope.onBodyClick()
+ element.on('click', function (e) {
+ if (
+ $(e.target).is(
+ '.rp-entry, .rp-entry-description, .rp-entry-body, .rp-entry-action-icon i'
+ )
+ ) {
+ return scope.onBodyClick()
+ }
+ })
+
+ scope.toggleCollapse = function () {
+ scope.isCollapsed = !scope.isCollapsed
+ return $timeout(() => scope.$emit('review-panel:layout'))
}
- })
- scope.toggleCollapse = function () {
- scope.isCollapsed = !scope.isCollapsed
- return $timeout(() => scope.$emit('review-panel:layout'))
- }
-
- return scope.$watch(
- 'entry.content.length',
- contentLength =>
- (scope.needsCollapsing = contentLength > scope.contentLimit)
- )
- },
- }),
+ return scope.$watch(
+ 'entry.content.length',
+ contentLength =>
+ (scope.needsCollapsing = contentLength > scope.contentLimit)
+ )
+ },
+ }
+ },
])
diff --git a/services/web/frontend/js/ide/review-panel/directives/commentEntry.js b/services/web/frontend/js/ide/review-panel/directives/commentEntry.js
index 6d091da9d9..befddc3bcd 100644
--- a/services/web/frontend/js/ide/review-panel/directives/commentEntry.js
+++ b/services/web/frontend/js/ide/review-panel/directives/commentEntry.js
@@ -13,83 +13,85 @@ import App from '../../../base'
export default App.directive('commentEntry', [
'$timeout',
- $timeout => ({
- restrict: 'E',
- templateUrl: 'commentEntryTemplate',
- scope: {
- entry: '=',
- threads: '=',
- permissions: '=',
- onResolve: '&',
- onReply: '&',
- onIndicatorClick: '&',
- onMouseEnter: '&',
- onMouseLeave: '&',
- onSaveEdit: '&',
- onDelete: '&',
- onBodyClick: '&',
- },
- link(scope, element, attrs) {
- scope.state = { animating: false }
+ function ($timeout) {
+ return {
+ restrict: 'E',
+ templateUrl: 'commentEntryTemplate',
+ scope: {
+ entry: '=',
+ threads: '=',
+ permissions: '=',
+ onResolve: '&',
+ onReply: '&',
+ onIndicatorClick: '&',
+ onMouseEnter: '&',
+ onMouseLeave: '&',
+ onSaveEdit: '&',
+ onDelete: '&',
+ onBodyClick: '&',
+ },
+ link(scope, element, attrs) {
+ scope.state = { animating: false }
- element.on('click', function (e) {
- if (
- $(e.target).is(
- '.rp-entry, .rp-comment-loaded, .rp-comment-content, .rp-comment-reply, .rp-entry-metadata'
- )
- ) {
- return scope.onBodyClick()
- }
- })
+ element.on('click', function (e) {
+ if (
+ $(e.target).is(
+ '.rp-entry, .rp-comment-loaded, .rp-comment-content, .rp-comment-reply, .rp-entry-metadata'
+ )
+ ) {
+ return scope.onBodyClick()
+ }
+ })
- scope.handleCommentReplyKeyPress = function (ev) {
- if (ev.keyCode === 13 && !ev.shiftKey && !ev.ctrlKey && !ev.metaKey) {
- ev.preventDefault()
- if (scope.entry.replyContent.length > 0) {
- ev.target.blur()
- return scope.onReply()
+ scope.handleCommentReplyKeyPress = function (ev) {
+ if (ev.keyCode === 13 && !ev.shiftKey && !ev.ctrlKey && !ev.metaKey) {
+ ev.preventDefault()
+ if (scope.entry.replyContent.length > 0) {
+ ev.target.blur()
+ return scope.onReply()
+ }
}
}
- }
- scope.animateAndCallOnResolve = function () {
- scope.state.animating = true
- element.find('.rp-entry').css('top', 0)
- $timeout(() => scope.onResolve(), 350)
- return true
- }
-
- scope.startEditing = function (comment) {
- comment.editing = true
- return setTimeout(() => scope.$emit('review-panel:layout'))
- }
-
- scope.saveEdit = function (comment) {
- comment.editing = false
- return scope.onSaveEdit({ comment })
- }
-
- scope.confirmDelete = function (comment) {
- comment.deleting = true
- return setTimeout(() => scope.$emit('review-panel:layout'))
- }
-
- scope.cancelDelete = function (comment) {
- comment.deleting = false
- return setTimeout(() => scope.$emit('review-panel:layout'))
- }
-
- scope.doDelete = function (comment) {
- comment.deleting = false
- return scope.onDelete({ comment })
- }
-
- return (scope.saveEditOnEnter = function (ev, comment) {
- if (ev.keyCode === 13 && !ev.shiftKey && !ev.ctrlKey && !ev.metaKey) {
- ev.preventDefault()
- return scope.saveEdit(comment)
+ scope.animateAndCallOnResolve = function () {
+ scope.state.animating = true
+ element.find('.rp-entry').css('top', 0)
+ $timeout(() => scope.onResolve(), 350)
+ return true
}
- })
- },
- }),
+
+ scope.startEditing = function (comment) {
+ comment.editing = true
+ return setTimeout(() => scope.$emit('review-panel:layout'))
+ }
+
+ scope.saveEdit = function (comment) {
+ comment.editing = false
+ return scope.onSaveEdit({ comment })
+ }
+
+ scope.confirmDelete = function (comment) {
+ comment.deleting = true
+ return setTimeout(() => scope.$emit('review-panel:layout'))
+ }
+
+ scope.cancelDelete = function (comment) {
+ comment.deleting = false
+ return setTimeout(() => scope.$emit('review-panel:layout'))
+ }
+
+ scope.doDelete = function (comment) {
+ comment.deleting = false
+ return scope.onDelete({ comment })
+ }
+
+ return (scope.saveEditOnEnter = function (ev, comment) {
+ if (ev.keyCode === 13 && !ev.shiftKey && !ev.ctrlKey && !ev.metaKey) {
+ ev.preventDefault()
+ return scope.saveEdit(comment)
+ }
+ })
+ },
+ }
+ },
])
diff --git a/services/web/frontend/js/ide/review-panel/directives/resolvedCommentEntry.js b/services/web/frontend/js/ide/review-panel/directives/resolvedCommentEntry.js
index 4404203937..a3c3ea589d 100644
--- a/services/web/frontend/js/ide/review-panel/directives/resolvedCommentEntry.js
+++ b/services/web/frontend/js/ide/review-panel/directives/resolvedCommentEntry.js
@@ -11,26 +11,28 @@
*/
import App from '../../../base'
-export default App.directive('resolvedCommentEntry', () => ({
- restrict: 'E',
- templateUrl: 'resolvedCommentEntryTemplate',
- scope: {
- thread: '=',
- permissions: '=',
- onUnresolve: '&',
- onDelete: '&',
- },
- link(scope, element, attrs) {
- scope.contentLimit = 40
- scope.needsCollapsing = false
- scope.isCollapsed = true
+export default App.directive('resolvedCommentEntry', function () {
+ return {
+ restrict: 'E',
+ templateUrl: 'resolvedCommentEntryTemplate',
+ scope: {
+ thread: '=',
+ permissions: '=',
+ onUnresolve: '&',
+ onDelete: '&',
+ },
+ link(scope, element, attrs) {
+ scope.contentLimit = 40
+ scope.needsCollapsing = false
+ scope.isCollapsed = true
- scope.toggleCollapse = () => (scope.isCollapsed = !scope.isCollapsed)
+ scope.toggleCollapse = () => (scope.isCollapsed = !scope.isCollapsed)
- return scope.$watch(
- 'thread.content.length',
- contentLength =>
- (scope.needsCollapsing = contentLength > scope.contentLimit)
- )
- },
-}))
+ return scope.$watch(
+ 'thread.content.length',
+ contentLength =>
+ (scope.needsCollapsing = contentLength > scope.contentLimit)
+ )
+ },
+ }
+})
diff --git a/services/web/frontend/js/ide/review-panel/directives/resolvedCommentsDropdown.js b/services/web/frontend/js/ide/review-panel/directives/resolvedCommentsDropdown.js
index 8ad9ff2e79..08d1e576c5 100644
--- a/services/web/frontend/js/ide/review-panel/directives/resolvedCommentsDropdown.js
+++ b/services/web/frontend/js/ide/review-panel/directives/resolvedCommentsDropdown.js
@@ -14,96 +14,98 @@ import _ from 'lodash'
*/
import App from '../../../base'
-export default App.directive('resolvedCommentsDropdown', () => ({
- restrict: 'E',
- templateUrl: 'resolvedCommentsDropdownTemplate',
- scope: {
- entries: '=',
- threads: '=',
- resolvedIds: '=',
- docs: '=',
- permissions: '=',
- onOpen: '&',
- onUnresolve: '&',
- onDelete: '&',
- isLoading: '=',
- },
+export default App.directive('resolvedCommentsDropdown', function () {
+ return {
+ restrict: 'E',
+ templateUrl: 'resolvedCommentsDropdownTemplate',
+ scope: {
+ entries: '=',
+ threads: '=',
+ resolvedIds: '=',
+ docs: '=',
+ permissions: '=',
+ onOpen: '&',
+ onUnresolve: '&',
+ onDelete: '&',
+ isLoading: '=',
+ },
- link(scope, element, attrs) {
- let filterResolvedComments
- scope.state = { isOpen: false }
+ link(scope, element, attrs) {
+ let filterResolvedComments
+ scope.state = { isOpen: false }
- scope.toggleOpenState = function () {
- scope.state.isOpen = !scope.state.isOpen
- if (scope.state.isOpen) {
- return scope.onOpen().then(() => filterResolvedComments())
+ scope.toggleOpenState = function () {
+ scope.state.isOpen = !scope.state.isOpen
+ if (scope.state.isOpen) {
+ return scope.onOpen().then(() => filterResolvedComments())
+ }
}
- }
- scope.resolvedComments = []
-
- scope.handleUnresolve = function (threadId) {
- scope.onUnresolve({ threadId })
- return (scope.resolvedComments = scope.resolvedComments.filter(
- c => c.threadId !== threadId
- ))
- }
-
- scope.handleDelete = function (entryId, docId, threadId) {
- scope.onDelete({ entryId, docId, threadId })
- return (scope.resolvedComments = scope.resolvedComments.filter(
- c => c.threadId !== threadId
- ))
- }
-
- const getDocNameById = function (docId) {
- const doc = _.find(scope.docs, doc => doc.doc.id === docId)
- if (doc != null) {
- return doc.path
- } else {
- return null
- }
- }
-
- return (filterResolvedComments = function () {
scope.resolvedComments = []
- return (() => {
- const result = []
- for (const docId in scope.entries) {
- const docEntries = scope.entries[docId]
- result.push(
- (() => {
- const result1 = []
- for (const entryId in docEntries) {
- const entry = docEntries[entryId]
- if (
- entry.type === 'comment' &&
- (scope.threads[entry.thread_id] != null
- ? scope.threads[entry.thread_id].resolved
- : undefined) != null
- ) {
- const resolvedComment = angular.copy(
- scope.threads[entry.thread_id]
- )
+ scope.handleUnresolve = function (threadId) {
+ scope.onUnresolve({ threadId })
+ return (scope.resolvedComments = scope.resolvedComments.filter(
+ c => c.threadId !== threadId
+ ))
+ }
- resolvedComment.content = entry.content
- resolvedComment.threadId = entry.thread_id
- resolvedComment.entryId = entryId
- resolvedComment.docId = docId
- resolvedComment.docName = getDocNameById(docId)
+ scope.handleDelete = function (entryId, docId, threadId) {
+ scope.onDelete({ entryId, docId, threadId })
+ return (scope.resolvedComments = scope.resolvedComments.filter(
+ c => c.threadId !== threadId
+ ))
+ }
- result1.push(scope.resolvedComments.push(resolvedComment))
- } else {
- result1.push(undefined)
- }
- }
- return result1
- })()
- )
+ const getDocNameById = function (docId) {
+ const doc = _.find(scope.docs, doc => doc.doc.id === docId)
+ if (doc != null) {
+ return doc.path
+ } else {
+ return null
}
- return result
- })()
- })
- },
-}))
+ }
+
+ return (filterResolvedComments = function () {
+ scope.resolvedComments = []
+
+ return (() => {
+ const result = []
+ for (const docId in scope.entries) {
+ const docEntries = scope.entries[docId]
+ result.push(
+ (() => {
+ const result1 = []
+ for (const entryId in docEntries) {
+ const entry = docEntries[entryId]
+ if (
+ entry.type === 'comment' &&
+ (scope.threads[entry.thread_id] != null
+ ? scope.threads[entry.thread_id].resolved
+ : undefined) != null
+ ) {
+ const resolvedComment = angular.copy(
+ scope.threads[entry.thread_id]
+ )
+
+ resolvedComment.content = entry.content
+ resolvedComment.threadId = entry.thread_id
+ resolvedComment.entryId = entryId
+ resolvedComment.docId = docId
+ resolvedComment.docName = getDocNameById(docId)
+
+ result1.push(scope.resolvedComments.push(resolvedComment))
+ } else {
+ result1.push(undefined)
+ }
+ }
+ return result1
+ })()
+ )
+ }
+ return result
+ })()
+ })
+ },
+ }
+})
diff --git a/services/web/frontend/js/ide/review-panel/directives/reviewPanelCollapseHeight.js b/services/web/frontend/js/ide/review-panel/directives/reviewPanelCollapseHeight.js
index 835c9f5d58..c808a5f9e2 100644
--- a/services/web/frontend/js/ide/review-panel/directives/reviewPanelCollapseHeight.js
+++ b/services/web/frontend/js/ide/review-panel/directives/reviewPanelCollapseHeight.js
@@ -12,26 +12,28 @@ import App from '../../../base'
export default App.directive('reviewPanelCollapseHeight', [
'$parse',
- $parse => ({
- restrict: 'A',
- link(scope, element, attrs) {
- return scope.$watch(
- () => $parse(attrs.reviewPanelCollapseHeight)(scope),
- function (shouldCollapse) {
- const neededHeight = element.prop('scrollHeight')
- if (neededHeight > 0) {
- if (shouldCollapse) {
- return element.animate({ height: 0 }, 150)
+ function ($parse) {
+ return {
+ restrict: 'A',
+ link(scope, element, attrs) {
+ return scope.$watch(
+ () => $parse(attrs.reviewPanelCollapseHeight)(scope),
+ function (shouldCollapse) {
+ const neededHeight = element.prop('scrollHeight')
+ if (neededHeight > 0) {
+ if (shouldCollapse) {
+ return element.animate({ height: 0 }, 150)
+ } else {
+ return element.animate({ height: neededHeight }, 150)
+ }
} else {
- return element.animate({ height: neededHeight }, 150)
- }
- } else {
- if (shouldCollapse) {
- return element.height(0)
+ if (shouldCollapse) {
+ return element.height(0)
+ }
}
}
- }
- )
- },
- }),
+ )
+ },
+ }
+ },
])
diff --git a/services/web/frontend/js/ide/review-panel/directives/reviewPanelSorted.js b/services/web/frontend/js/ide/review-panel/directives/reviewPanelSorted.js
index 6c69f26562..b706148816 100644
--- a/services/web/frontend/js/ide/review-panel/directives/reviewPanelSorted.js
+++ b/services/web/frontend/js/ide/review-panel/directives/reviewPanelSorted.js
@@ -16,285 +16,289 @@
import App from '../../../base'
import { debugConsole } from '@/utils/debugging'
-export default App.directive('reviewPanelSorted', () => ({
- link(scope, element, attrs) {
- let previous_focused_entry_index = 0
+export default App.directive('reviewPanelSorted', function () {
+ return {
+ link(scope, element, attrs) {
+ let previous_focused_entry_index = 0
- const applyEntryVisibility = function (entry) {
- const visible = entry.scope.entry.screenPos
- if (visible) {
- entry.$wrapper_el.removeClass('rp-entry-hidden')
- } else {
- entry.$wrapper_el.addClass('rp-entry-hidden')
- }
- return visible
- }
-
- const layout = function (animate) {
- let entry,
- height,
- i,
- original_top,
- overflowTop,
- screenPosHeight,
- OVERVIEW_TOGGLE_HEIGHT,
- PADDING,
- TOOLBAR_HEIGHT,
- top
- if (animate == null) {
- animate = true
- }
- if (animate) {
- element.removeClass('no-animate')
- } else {
- element.addClass('no-animate')
- }
- if (scope.ui.reviewPanelOpen) {
- PADDING = 8
- TOOLBAR_HEIGHT = 38
- OVERVIEW_TOGGLE_HEIGHT = 57
- } else {
- PADDING = 4
- TOOLBAR_HEIGHT = 4
- OVERVIEW_TOGGLE_HEIGHT = 0
+ const applyEntryVisibility = function (entry) {
+ const visible = entry.scope.entry.screenPos
+ if (visible) {
+ entry.$wrapper_el.removeClass('rp-entry-hidden')
+ } else {
+ entry.$wrapper_el.addClass('rp-entry-hidden')
+ }
+ return visible
}
- const entries = []
- for (const el of Array.from(element.find('.rp-entry-wrapper'))) {
- entry = {
- $wrapper_el: $(el),
- $indicator_el: $(el).find('.rp-entry-indicator'),
- $box_el: $(el).find('.rp-entry'),
- $callout_el: $(el).find('.rp-entry-callout'),
- scope: angular.element(el).scope(),
+ const layout = function (animate) {
+ let entry,
+ height,
+ i,
+ original_top,
+ overflowTop,
+ screenPosHeight,
+ OVERVIEW_TOGGLE_HEIGHT,
+ PADDING,
+ TOOLBAR_HEIGHT,
+ top
+ if (animate == null) {
+ animate = true
+ }
+ if (animate) {
+ element.removeClass('no-animate')
+ } else {
+ element.addClass('no-animate')
}
if (scope.ui.reviewPanelOpen) {
- entry.$layout_el = entry.$box_el
+ PADDING = 8
+ TOOLBAR_HEIGHT = 38
+ OVERVIEW_TOGGLE_HEIGHT = 57
} else {
- entry.$layout_el = entry.$indicator_el
+ PADDING = 4
+ TOOLBAR_HEIGHT = 4
+ OVERVIEW_TOGGLE_HEIGHT = 0
}
- entry.height = entry.$layout_el.height() // Do all of our DOM reads first for performance, see http://wilsonpage.co.uk/preventing-layout-thrashing/
- entries.push(entry)
- }
- entries.sort((a, b) => a.scope.entry.offset - b.scope.entry.offset)
- if (entries.length === 0) {
- return
- }
-
- const line_height = scope.reviewPanel.rendererData.lineHeight
-
- let focused_entry_index = Math.min(
- previous_focused_entry_index,
- entries.length - 1
- )
- for (i = 0; i < entries.length; i++) {
- entry = entries[i]
- if (entry.scope.entry.focused) {
- focused_entry_index = i
- break
+ const entries = []
+ for (const el of Array.from(element.find('.rp-entry-wrapper'))) {
+ entry = {
+ $wrapper_el: $(el),
+ $indicator_el: $(el).find('.rp-entry-indicator'),
+ $box_el: $(el).find('.rp-entry'),
+ $callout_el: $(el).find('.rp-entry-callout'),
+ scope: angular.element(el).scope(),
+ }
+ if (scope.ui.reviewPanelOpen) {
+ entry.$layout_el = entry.$box_el
+ } else {
+ entry.$layout_el = entry.$indicator_el
+ }
+ entry.height = entry.$layout_el.height() // Do all of our DOM reads first for performance, see http://wilsonpage.co.uk/preventing-layout-thrashing/
+ entries.push(entry)
}
- }
+ entries.sort((a, b) => a.scope.entry.offset - b.scope.entry.offset)
- const focused_entry = entries[focused_entry_index]
- const focusedEntryVisible = applyEntryVisibility(focused_entry)
-
- // If the focused entry has no screenPos, we can't position other entries
- // relative to it, so we position all other entries as though the focused
- // entry is at the top and they all follow it
- const entries_after = focusedEntryVisible
- ? entries.slice(focused_entry_index + 1)
- : [...entries]
- const entries_before = focusedEntryVisible
- ? entries.slice(0, focused_entry_index)
- : []
- previous_focused_entry_index = focused_entry_index
-
- debugConsole.log('focused_entry_index', focused_entry_index)
-
- const positionLayoutEl = function (
- $callout_el,
- original_top,
- top,
- height
- ) {
- if (original_top <= top) {
- $callout_el.removeClass('rp-entry-callout-inverted')
- return $callout_el.css({
- top: original_top + height - 1,
- height: top - original_top,
- })
- } else {
- $callout_el.addClass('rp-entry-callout-inverted')
- return $callout_el.css({
- top: top + height,
- height: original_top - top,
- })
+ if (entries.length === 0) {
+ return
}
- }
- // Put the focused entry as close to where it wants to be as possible
- let focused_entry_top = 0
- let previousBottom = 0
+ const line_height = scope.reviewPanel.rendererData.lineHeight
- if (focusedEntryVisible) {
- const focusedEntryScreenPos = focused_entry.scope.entry.screenPos
- focused_entry_top = Math.max(focusedEntryScreenPos.y, TOOLBAR_HEIGHT)
- focused_entry.$box_el.css({
- top: focused_entry_top,
- // The entry element is invisible by default, to avoid flickering when positioning for
- // the first time. Here we make sure it becomes visible after having a "top" value.
- visibility: 'visible',
- })
- focused_entry.$indicator_el.css({ top: focused_entry_top })
- // use screenPos.height if set
- screenPosHeight = focusedEntryScreenPos.height ?? line_height
- positionLayoutEl(
- focused_entry.$callout_el,
- focusedEntryScreenPos.y,
- focused_entry_top,
- screenPosHeight
+ let focused_entry_index = Math.min(
+ previous_focused_entry_index,
+ entries.length - 1
)
- previousBottom = focused_entry_top + focused_entry.$layout_el.height()
- }
+ for (i = 0; i < entries.length; i++) {
+ entry = entries[i]
+ if (entry.scope.entry.focused) {
+ focused_entry_index = i
+ break
+ }
+ }
- for (entry of entries_after) {
- const entryVisible = applyEntryVisibility(entry)
- if (entryVisible) {
- original_top = entry.scope.entry.screenPos.y
- // use screenPos.height if set
- screenPosHeight = entry.scope.entry.screenPos.height ?? line_height
- ;({ height } = entry)
- top = Math.max(original_top, previousBottom + PADDING)
- previousBottom = top + height
- entry.$box_el.css({
- top,
+ const focused_entry = entries[focused_entry_index]
+ const focusedEntryVisible = applyEntryVisibility(focused_entry)
+
+ // If the focused entry has no screenPos, we can't position other entries
+ // relative to it, so we position all other entries as though the focused
+ // entry is at the top and they all follow it
+ const entries_after = focusedEntryVisible
+ ? entries.slice(focused_entry_index + 1)
+ : [...entries]
+ const entries_before = focusedEntryVisible
+ ? entries.slice(0, focused_entry_index)
+ : []
+ previous_focused_entry_index = focused_entry_index
+
+ debugConsole.log('focused_entry_index', focused_entry_index)
+
+ const positionLayoutEl = function (
+ $callout_el,
+ original_top,
+ top,
+ height
+ ) {
+ if (original_top <= top) {
+ $callout_el.removeClass('rp-entry-callout-inverted')
+ return $callout_el.css({
+ top: original_top + height - 1,
+ height: top - original_top,
+ })
+ } else {
+ $callout_el.addClass('rp-entry-callout-inverted')
+ return $callout_el.css({
+ top: top + height,
+ height: original_top - top,
+ })
+ }
+ }
+
+ // Put the focused entry as close to where it wants to be as possible
+ let focused_entry_top = 0
+ let previousBottom = 0
+
+ if (focusedEntryVisible) {
+ const focusedEntryScreenPos = focused_entry.scope.entry.screenPos
+ focused_entry_top = Math.max(focusedEntryScreenPos.y, TOOLBAR_HEIGHT)
+ focused_entry.$box_el.css({
+ top: focused_entry_top,
// The entry element is invisible by default, to avoid flickering when positioning for
// the first time. Here we make sure it becomes visible after having a "top" value.
visibility: 'visible',
})
- entry.$indicator_el.css({ top })
- positionLayoutEl(
- entry.$callout_el,
- original_top,
- top,
- screenPosHeight
- )
- }
- debugConsole.log('ENTRY', { entry: entry.scope.entry, top })
- }
-
- let previousTop = focused_entry_top
- entries_before.reverse() // Work through backwards, starting with the one just above
- for (entry of entries_before) {
- const entryVisible = applyEntryVisibility(entry)
- if (entryVisible) {
- original_top = entry.scope.entry.screenPos.y
+ focused_entry.$indicator_el.css({ top: focused_entry_top })
// use screenPos.height if set
- screenPosHeight = entry.scope.entry.screenPos.height ?? line_height
- ;({ height } = entry)
- const original_bottom = original_top + height
- const bottom = Math.min(original_bottom, previousTop - PADDING)
- top = bottom - height
- previousTop = top
- entry.$box_el.css({
- top,
- // The entry element is invisible by default, to avoid flickering when positioning for
- // the first time. Here we make sure it becomes visible after having a "top" value.
- visibility: 'visible',
- })
- entry.$indicator_el.css({ top })
+ screenPosHeight = focusedEntryScreenPos.height ?? line_height
positionLayoutEl(
- entry.$callout_el,
- original_top,
- top,
+ focused_entry.$callout_el,
+ focusedEntryScreenPos.y,
+ focused_entry_top,
screenPosHeight
)
+ previousBottom = focused_entry_top + focused_entry.$layout_el.height()
}
- debugConsole.log('ENTRY', { entry: entry.scope.entry, top })
- }
- const lastTop = top
- if (lastTop < TOOLBAR_HEIGHT) {
- overflowTop = -lastTop + TOOLBAR_HEIGHT
- } else {
- overflowTop = 0
- }
+ for (entry of entries_after) {
+ const entryVisible = applyEntryVisibility(entry)
+ if (entryVisible) {
+ original_top = entry.scope.entry.screenPos.y
+ // use screenPos.height if set
+ screenPosHeight = entry.scope.entry.screenPos.height ?? line_height
+ ;({ height } = entry)
+ top = Math.max(original_top, previousBottom + PADDING)
+ previousBottom = top + height
+ entry.$box_el.css({
+ top,
+ // The entry element is invisible by default, to avoid flickering when positioning for
+ // the first time. Here we make sure it becomes visible after having a "top" value.
+ visibility: 'visible',
+ })
+ entry.$indicator_el.css({ top })
+ positionLayoutEl(
+ entry.$callout_el,
+ original_top,
+ top,
+ screenPosHeight
+ )
+ }
+ debugConsole.log('ENTRY', { entry: entry.scope.entry, top })
+ }
- return scope.$emit('review-panel:sizes', {
- overflowTop,
- height: previousBottom + OVERVIEW_TOGGLE_HEIGHT,
- })
- }
+ let previousTop = focused_entry_top
+ entries_before.reverse() // Work through backwards, starting with the one just above
+ for (entry of entries_before) {
+ const entryVisible = applyEntryVisibility(entry)
+ if (entryVisible) {
+ original_top = entry.scope.entry.screenPos.y
+ // use screenPos.height if set
+ screenPosHeight = entry.scope.entry.screenPos.height ?? line_height
+ ;({ height } = entry)
+ const original_bottom = original_top + height
+ const bottom = Math.min(original_bottom, previousTop - PADDING)
+ top = bottom - height
+ previousTop = top
+ entry.$box_el.css({
+ top,
+ // The entry element is invisible by default, to avoid flickering when positioning for
+ // the first time. Here we make sure it becomes visible after having a "top" value.
+ visibility: 'visible',
+ })
+ entry.$indicator_el.css({ top })
+ positionLayoutEl(
+ entry.$callout_el,
+ original_top,
+ top,
+ screenPosHeight
+ )
+ }
+ debugConsole.log('ENTRY', { entry: entry.scope.entry, top })
+ }
- scope.$applyAsync(() => layout())
+ const lastTop = top
+ if (lastTop < TOOLBAR_HEIGHT) {
+ overflowTop = -lastTop + TOOLBAR_HEIGHT
+ } else {
+ overflowTop = 0
+ }
- scope.$on('review-panel:layout', function (e, animate) {
- if (animate == null) {
- animate = true
- }
- return scope.$applyAsync(() => layout(animate))
- })
-
- scope.$watch('reviewPanel.rendererData.lineHeight', () => layout())
-
- // # Scroll lock with Ace
- const scroller = element
- const list = element.find('.rp-entry-list-inner')
-
- // If we listen for scroll events in the review panel natively, then with a Mac trackpad
- // the scroll is very smooth (natively done I'd guess), but we don't get polled regularly
- // enough to keep Ace in step, and it noticeably lags. If instead, we borrow the manual
- // mousewheel/trackpad scrolling behaviour from Ace, and turn mousewheel events into
- // scroll events ourselves, then it makes the review panel slightly less smooth (barely)
- // noticeable, but keeps it perfectly in step with Ace.
- ace
- .require('ace/lib/event')
- .addMouseWheelListener(scroller[0], function (e) {
- const deltaY = e.wheelY
- const old_top = parseInt(list.css('top'))
- const top = old_top - deltaY * 4
- scrollAce(-top)
- dispatchScrollEvent(deltaY * 4)
- return e.preventDefault()
- })
-
- // We always scroll by telling Ace to scroll and then updating the
- // review panel. This lets Ace manage the size of the scroller and
- // when it overflows.
- let ignoreNextAceEvent = false
-
- const scrollPanel = function (scrollTop, height) {
- if (ignoreNextAceEvent) {
- return (ignoreNextAceEvent = false)
- } else {
- list.height(height)
- return list.css({ top: -scrollTop })
- }
- }
-
- const scrollAce = scrollTop =>
- scope.reviewPanelEventsBridge.emit('externalScroll', scrollTop)
-
- scope.reviewPanelEventsBridge.on('aceScroll', scrollPanel)
- scope.$on('$destroy', () => scope.reviewPanelEventsBridge.off('aceScroll'))
-
- // receive the scroll position from the CodeMirror 6 track changes extension
- window.addEventListener('editor:scroll', event => {
- const { scrollTop, height, paddingTop } = event.detail
-
- scrollPanel(scrollTop - paddingTop, height)
- })
-
- // Send scroll delta to the CodeMirror 6 track changes extension
- const dispatchScrollEvent = scrollTopDelta => {
- window.dispatchEvent(
- new CustomEvent('review-panel:event', {
- detail: { type: 'scroll', payload: scrollTopDelta },
+ return scope.$emit('review-panel:sizes', {
+ overflowTop,
+ height: previousBottom + OVERVIEW_TOGGLE_HEIGHT,
})
- )
- }
+ }
- return scope.reviewPanelEventsBridge.emit('refreshScrollPosition')
- },
-}))
+ scope.$applyAsync(() => layout())
+
+ scope.$on('review-panel:layout', function (e, animate) {
+ if (animate == null) {
+ animate = true
+ }
+ return scope.$applyAsync(() => layout(animate))
+ })
+
+ scope.$watch('reviewPanel.rendererData.lineHeight', () => layout())
+
+ // # Scroll lock with Ace
+ const scroller = element
+ const list = element.find('.rp-entry-list-inner')
+
+ // If we listen for scroll events in the review panel natively, then with a Mac trackpad
+ // the scroll is very smooth (natively done I'd guess), but we don't get polled regularly
+ // enough to keep Ace in step, and it noticeably lags. If instead, we borrow the manual
+ // mousewheel/trackpad scrolling behaviour from Ace, and turn mousewheel events into
+ // scroll events ourselves, then it makes the review panel slightly less smooth (barely)
+ // noticeable, but keeps it perfectly in step with Ace.
+ ace
+ .require('ace/lib/event')
+ .addMouseWheelListener(scroller[0], function (e) {
+ const deltaY = e.wheelY
+ const old_top = parseInt(list.css('top'))
+ const top = old_top - deltaY * 4
+ scrollAce(-top)
+ dispatchScrollEvent(deltaY * 4)
+ return e.preventDefault()
+ })
+
+ // We always scroll by telling Ace to scroll and then updating the
+ // review panel. This lets Ace manage the size of the scroller and
+ // when it overflows.
+ let ignoreNextAceEvent = false
+
+ const scrollPanel = function (scrollTop, height) {
+ if (ignoreNextAceEvent) {
+ return (ignoreNextAceEvent = false)
+ } else {
+ list.height(height)
+ return list.css({ top: -scrollTop })
+ }
+ }
+
+ const scrollAce = scrollTop =>
+ scope.reviewPanelEventsBridge.emit('externalScroll', scrollTop)
+
+ scope.reviewPanelEventsBridge.on('aceScroll', scrollPanel)
+ scope.$on('$destroy', () =>
+ scope.reviewPanelEventsBridge.off('aceScroll')
+ )
+
+ // receive the scroll position from the CodeMirror 6 track changes extension
+ window.addEventListener('editor:scroll', event => {
+ const { scrollTop, height, paddingTop } = event.detail
+
+ scrollPanel(scrollTop - paddingTop, height)
+ })
+
+ // Send scroll delta to the CodeMirror 6 track changes extension
+ const dispatchScrollEvent = scrollTopDelta => {
+ window.dispatchEvent(
+ new CustomEvent('review-panel:event', {
+ detail: { type: 'scroll', payload: scrollTopDelta },
+ })
+ )
+ }
+
+ return scope.reviewPanelEventsBridge.emit('refreshScrollPosition')
+ },
+ }
+})
diff --git a/services/web/frontend/js/ide/review-panel/directives/reviewPanelToggle.js b/services/web/frontend/js/ide/review-panel/directives/reviewPanelToggle.js
index ac0ad18cbf..810e4d16dc 100644
--- a/services/web/frontend/js/ide/review-panel/directives/reviewPanelToggle.js
+++ b/services/web/frontend/js/ide/review-panel/directives/reviewPanelToggle.js
@@ -12,40 +12,42 @@
*/
import App from '../../../base'
-export default App.directive('reviewPanelToggle', () => ({
- restrict: 'E',
- scope: {
- onToggle: '&',
- ngModel: '=',
- valWhenUndefined: '=?',
- isDisabled: '=?',
- onDisabledClick: '&?',
- description: '@',
- },
- link(scope) {
- if (scope.disabled == null) {
- scope.disabled = false
- }
- scope.onChange = (...args) => scope.onToggle({ isOn: scope.localModel })
- scope.handleClick = function () {
- if (scope.disabled && scope.onDisabledClick != null) {
- return scope.onDisabledClick()
+export default App.directive('reviewPanelToggle', function () {
+ return {
+ restrict: 'E',
+ scope: {
+ onToggle: '&',
+ ngModel: '=',
+ valWhenUndefined: '=?',
+ isDisabled: '=?',
+ onDisabledClick: '&?',
+ description: '@',
+ },
+ link(scope) {
+ if (scope.disabled == null) {
+ scope.disabled = false
}
- }
- scope.localModel = scope.ngModel
- return scope.$watch('ngModel', function (value) {
- if (scope.valWhenUndefined != null && value == null) {
- value = scope.valWhenUndefined
+ scope.onChange = (...args) => scope.onToggle({ isOn: scope.localModel })
+ scope.handleClick = function () {
+ if (scope.disabled && scope.onDisabledClick != null) {
+ return scope.onDisabledClick()
+ }
}
- return (scope.localModel = value)
- })
- },
+ scope.localModel = scope.ngModel
+ return scope.$watch('ngModel', function (value) {
+ if (scope.valWhenUndefined != null && value == null) {
+ value = scope.valWhenUndefined
+ }
+ return (scope.localModel = value)
+ })
+ },
- template: `\
+ template: `\
\
`,
-}))
+ }
+})