Merge pull request #1925 from overleaf/as-babel-plugin-angular-annotate

Enable Angular "annotations" via Babel plugin

GitOrigin-RevId: 343610025e3ba791fb284a0b696a1ea33e125a80
This commit is contained in:
Eric Mc Sween 2019-07-16 10:13:18 +01:00 committed by sharelatex
parent d7549544d6
commit 65849456d0
43 changed files with 5168 additions and 5810 deletions

View file

@ -2,5 +2,6 @@
"presets": [
"react",
["env", { "modules": false }]
]
],
"plugins": ["angularjs-annotate"]
}

View file

@ -23,9 +23,6 @@ module.exports = function(grunt) {
compile: {
options: {
optimize: 'uglify2',
uglify2: {
mangle: false
},
appDir: 'public/js',
baseUrl: './',
dir: 'public/minjs',

File diff suppressed because it is too large Load diff

View file

@ -119,6 +119,7 @@
"babel-cli": "^6.26.0",
"babel-core": "^6.26.0",
"babel-loader": "^7.1.2",
"babel-plugin-angularjs-annotate": "^0.10.0",
"babel-preset-env": "^1.6.1",
"babel-preset-react": "^6.16.0",
"bunyan": "0.22.1",

View file

@ -9,17 +9,15 @@
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
*/
define(['base'], App =>
App.directive('equals', [
() => ({
require: 'ngModel',
link(scope, elem, attrs, ctrl) {
const firstField = `#${attrs.equals}`
return elem.add(firstField).on('keyup', () =>
scope.$apply(function() {
const equal = elem.val() === $(firstField).val()
return ctrl.$setValidity('areEqual', equal)
})
)
}
})
]))
App.directive('equals', () => ({
require: 'ngModel',
link(scope, elem, attrs, ctrl) {
const firstField = `#${attrs.equals}`
return elem.add(firstField).on('keyup', () =>
scope.$apply(function() {
const equal = elem.val() === $(firstField).val()
return ctrl.$setValidity('areEqual', equal)
})
)
}
})))

View file

@ -35,72 +35,67 @@ const isInViewport = function(element) {
}
define(['base'], App =>
App.directive('eventTracking', [
'event_tracking',
event_tracking => ({
scope: {
eventTracking: '@',
eventSegmentation: '=?'
},
link(scope, element, attrs) {
const sendGA = attrs.eventTrackingGa || false
const sendMB = attrs.eventTrackingMb || false
const sendMBFunction = attrs.eventTrackingSendOnce
? 'sendMBOnce'
: 'sendMB'
const sendGAFunction = attrs.eventTrackingSendOnce
? 'sendGAOnce'
: 'send'
const segmentation = scope.eventSegmentation || {}
segmentation.page = window.location.pathname
App.directive('eventTracking', event_tracking => ({
scope: {
eventTracking: '@',
eventSegmentation: '=?'
},
link(scope, element, attrs) {
const sendGA = attrs.eventTrackingGa || false
const sendMB = attrs.eventTrackingMb || false
const sendMBFunction = attrs.eventTrackingSendOnce
? 'sendMBOnce'
: 'sendMB'
const sendGAFunction = attrs.eventTrackingSendOnce ? 'sendGAOnce' : 'send'
const segmentation = scope.eventSegmentation || {}
segmentation.page = window.location.pathname
const sendEvent = function(scrollEvent) {
/*
const sendEvent = function(scrollEvent) {
/*
@param {boolean} scrollEvent Use to unbind scroll event
*/
if (sendMB) {
event_tracking[sendMBFunction](scope.eventTracking, segmentation)
}
if (sendGA) {
event_tracking[sendGAFunction](
attrs.eventTrackingGa,
attrs.eventTrackingAction || scope.eventTracking,
attrs.eventTrackingLabel || ''
)
}
if (scrollEvent) {
return $(window).unbind('resize scroll')
}
if (sendMB) {
event_tracking[sendMBFunction](scope.eventTracking, segmentation)
}
if (attrs.eventTrackingTrigger === 'load') {
return sendEvent()
} else if (attrs.eventTrackingTrigger === 'click') {
return element.on('click', e => sendEvent())
} else if (attrs.eventTrackingTrigger === 'hover') {
let timer = null
let timeoutAmt = 500
if (attrs.eventHoverAmt) {
timeoutAmt = parseInt(attrs.eventHoverAmt, 10)
}
return element
.on('mouseenter', function() {
timer = setTimeout(() => sendEvent(), timeoutAmt)
})
.on('mouseleave', () => clearTimeout(timer))
} else if (attrs.eventTrackingTrigger === 'scroll') {
if (!event_tracking.eventInCache(scope.eventTracking)) {
return $(window).on('resize scroll', () =>
_.throttle(
isInViewport(element) &&
!event_tracking.eventInCache(scope.eventTracking)
? sendEvent(true)
: undefined,
500
)
)
}
if (sendGA) {
event_tracking[sendGAFunction](
attrs.eventTrackingGa,
attrs.eventTrackingAction || scope.eventTracking,
attrs.eventTrackingLabel || ''
)
}
if (scrollEvent) {
return $(window).unbind('resize scroll')
}
}
})
]))
if (attrs.eventTrackingTrigger === 'load') {
return sendEvent()
} else if (attrs.eventTrackingTrigger === 'click') {
return element.on('click', e => sendEvent())
} else if (attrs.eventTrackingTrigger === 'hover') {
let timer = null
let timeoutAmt = 500
if (attrs.eventHoverAmt) {
timeoutAmt = parseInt(attrs.eventHoverAmt, 10)
}
return element
.on('mouseenter', function() {
timer = setTimeout(() => sendEvent(), timeoutAmt)
})
.on('mouseleave', () => clearTimeout(timer))
} else if (attrs.eventTrackingTrigger === 'scroll') {
if (!event_tracking.eventInCache(scope.eventTracking)) {
return $(window).on('resize scroll', () =>
_.throttle(
isInViewport(element) &&
!event_tracking.eventInCache(scope.eventTracking)
? sendEvent(true)
: undefined,
500
)
)
}
}
}
})))

View file

@ -1,197 +1,193 @@
define(['base', 'moment'], (App, moment) =>
App.controller('BinaryFileController', [
'$scope',
'$rootScope',
'$http',
'$timeout',
'$element',
'ide',
'waitFor',
function($scope, $rootScope, $http, $timeout, $element, ide, waitFor) {
const MAX_FILE_SIZE = 2 * 1024 * 1024
const MAX_URL_LENGTH = 60
const FRONT_OF_URL_LENGTH = 35
const FILLER = '...'
const TAIL_OF_URL_LENGTH =
MAX_URL_LENGTH - FRONT_OF_URL_LENGTH - FILLER.length
App.controller('BinaryFileController', function(
$scope,
$rootScope,
$http,
$timeout,
$element,
ide,
waitFor
) {
const MAX_FILE_SIZE = 2 * 1024 * 1024
const MAX_URL_LENGTH = 60
const FRONT_OF_URL_LENGTH = 35
const FILLER = '...'
const TAIL_OF_URL_LENGTH =
MAX_URL_LENGTH - FRONT_OF_URL_LENGTH - FILLER.length
const textExtensions = ['bib', 'tex', 'txt', 'cls', 'sty']
const imageExtensions = ['png', 'jpg', 'jpeg', 'gif']
const previewableExtensions = []
const textExtensions = ['bib', 'tex', 'txt', 'cls', 'sty']
const imageExtensions = ['png', 'jpg', 'jpeg', 'gif']
const previewableExtensions = []
const extension = file =>
file.name
.split('.')
.pop()
.toLowerCase()
const extension = file =>
file.name
.split('.')
.pop()
.toLowerCase()
$scope.isTextFile = () =>
textExtensions.indexOf(extension($scope.openFile)) > -1
$scope.isImageFile = () =>
imageExtensions.indexOf(extension($scope.openFile)) > -1
$scope.isPreviewableFile = () =>
previewableExtensions.indexOf(extension($scope.openFile)) > -1
$scope.isUnpreviewableFile = () =>
!$scope.isTextFile() &&
!$scope.isImageFile() &&
!$scope.isPreviewableFile()
$scope.isTextFile = () =>
textExtensions.indexOf(extension($scope.openFile)) > -1
$scope.isImageFile = () =>
imageExtensions.indexOf(extension($scope.openFile)) > -1
$scope.isPreviewableFile = () =>
previewableExtensions.indexOf(extension($scope.openFile)) > -1
$scope.isUnpreviewableFile = () =>
!$scope.isTextFile() &&
!$scope.isImageFile() &&
!$scope.isPreviewableFile()
$scope.textPreview = {
loading: false,
shouldShowDots: false,
error: false,
data: null
}
$scope.refreshing = false
$scope.refreshError = null
$scope.displayUrl = function(url) {
if (url == null) {
return
}
if (url.length > MAX_URL_LENGTH) {
const front = url.slice(0, FRONT_OF_URL_LENGTH)
const tail = url.slice(url.length - TAIL_OF_URL_LENGTH)
return front + FILLER + tail
}
return url
}
$scope.refreshFile = function(file) {
$scope.refreshing = true
$scope.refreshError = null
ide.fileTreeManager
.refreshLinkedFile(file)
.then(function(response) {
const { data } = response
const newFileId = data.new_file_id
$timeout(
() =>
waitFor(
() => ide.fileTreeManager.findEntityById(newFileId),
5000
)
.then(newFile => ide.binaryFilesManager.openFile(newFile))
.catch(err => console.warn(err)),
0
)
$scope.refreshError = null
})
.catch(response => ($scope.refreshError = response.data))
.finally(() => {
$scope.refreshing = false
const provider = file.linkedFileData.provider
if (
provider === 'mendeley' ||
provider === 'zotero' ||
file.name.match(/^.*\.bib$/)
) {
ide.$scope.$emit('references:should-reindex', {})
}
})
}
// Callback fired when the `img` tag fails to load,
// `failedLoad` used to show the "No Preview" message
$scope.failedLoad = false
window.sl_binaryFilePreviewError = () => {
$scope.failedLoad = true
$scope.$apply()
}
// Callback fired when the `img` tag is done loading,
// `imgLoaded` is used to show the spinner gif while loading
$scope.imgLoaded = false
window.sl_binaryFilePreviewLoaded = () => {
$scope.imgLoaded = true
$scope.$apply()
}
if ($scope.isTextFile()) {
loadTextFilePreview()
}
function loadTextFilePreview() {
const url = `/project/${window.project_id}/file/${$scope.openFile.id}`
let truncated = false
displayPreviewLoading()
getFileSize(url)
.then(fileSize => {
const opts = {}
if (fileSize > MAX_FILE_SIZE) {
truncated = true
opts.maxSize = MAX_FILE_SIZE
}
return getFileContents(url, opts)
})
.then(contents => {
const displayedContents = truncated
? truncateFileContents(contents)
: contents
displayPreview(displayedContents, truncated)
})
.catch(err => {
console.error(err)
displayPreviewError()
})
}
function getFileSize(url) {
return $http.head(url).then(response => {
const size = parseInt(response.headers('Content-Length'), 10)
if (isNaN(size)) {
throw new Error('Could not parse Content-Length header')
}
return size
})
}
function getFileContents(url, opts = {}) {
const { maxSize } = opts
if (maxSize != null) {
url += `?range=0-${maxSize}`
}
return $http
.get(url, {
transformResponse: null // Don't parse JSON
})
.then(response => {
return response.data
})
}
function truncateFileContents(contents) {
return contents.replace(/\n.*$/, '')
}
function displayPreviewLoading() {
$scope.textPreview.data = null
$scope.textPreview.loading = true
$scope.textPreview.shouldShowDots = false
$scope.$apply()
}
function displayPreview(contents, truncated) {
$scope.textPreview.error = false
$scope.textPreview.data = contents
$scope.textPreview.shouldShowDots = truncated
$timeout(setPreviewHeight, 0)
}
function displayPreviewError() {
$scope.textPreview.error = true
$scope.textPreview.loading = false
}
function setPreviewHeight() {
const $preview = $element.find('.text-preview .scroll-container')
const $footer = $element.find('.binary-file-footer')
const maxHeight = $element.height() - $footer.height() - 14 // borders + margin
$preview.css({ 'max-height': maxHeight })
// Don't show the preview until we've set the height, otherwise we jump around
$scope.textPreview.loading = false
}
$scope.textPreview = {
loading: false,
shouldShowDots: false,
error: false,
data: null
}
]))
$scope.refreshing = false
$scope.refreshError = null
$scope.displayUrl = function(url) {
if (url == null) {
return
}
if (url.length > MAX_URL_LENGTH) {
const front = url.slice(0, FRONT_OF_URL_LENGTH)
const tail = url.slice(url.length - TAIL_OF_URL_LENGTH)
return front + FILLER + tail
}
return url
}
$scope.refreshFile = function(file) {
$scope.refreshing = true
$scope.refreshError = null
ide.fileTreeManager
.refreshLinkedFile(file)
.then(function(response) {
const { data } = response
const newFileId = data.new_file_id
$timeout(
() =>
waitFor(() => ide.fileTreeManager.findEntityById(newFileId), 5000)
.then(newFile => ide.binaryFilesManager.openFile(newFile))
.catch(err => console.warn(err)),
0
)
$scope.refreshError = null
})
.catch(response => ($scope.refreshError = response.data))
.finally(() => {
$scope.refreshing = false
const provider = file.linkedFileData.provider
if (
provider === 'mendeley' ||
provider === 'zotero' ||
file.name.match(/^.*\.bib$/)
) {
ide.$scope.$emit('references:should-reindex', {})
}
})
}
// Callback fired when the `img` tag fails to load,
// `failedLoad` used to show the "No Preview" message
$scope.failedLoad = false
window.sl_binaryFilePreviewError = () => {
$scope.failedLoad = true
$scope.$apply()
}
// Callback fired when the `img` tag is done loading,
// `imgLoaded` is used to show the spinner gif while loading
$scope.imgLoaded = false
window.sl_binaryFilePreviewLoaded = () => {
$scope.imgLoaded = true
$scope.$apply()
}
if ($scope.isTextFile()) {
loadTextFilePreview()
}
function loadTextFilePreview() {
const url = `/project/${window.project_id}/file/${$scope.openFile.id}`
let truncated = false
displayPreviewLoading()
getFileSize(url)
.then(fileSize => {
const opts = {}
if (fileSize > MAX_FILE_SIZE) {
truncated = true
opts.maxSize = MAX_FILE_SIZE
}
return getFileContents(url, opts)
})
.then(contents => {
const displayedContents = truncated
? truncateFileContents(contents)
: contents
displayPreview(displayedContents, truncated)
})
.catch(err => {
console.error(err)
displayPreviewError()
})
}
function getFileSize(url) {
return $http.head(url).then(response => {
const size = parseInt(response.headers('Content-Length'), 10)
if (isNaN(size)) {
throw new Error('Could not parse Content-Length header')
}
return size
})
}
function getFileContents(url, opts = {}) {
const { maxSize } = opts
if (maxSize != null) {
url += `?range=0-${maxSize}`
}
return $http
.get(url, {
transformResponse: null // Don't parse JSON
})
.then(response => {
return response.data
})
}
function truncateFileContents(contents) {
return contents.replace(/\n.*$/, '')
}
function displayPreviewLoading() {
$scope.textPreview.data = null
$scope.textPreview.loading = true
$scope.textPreview.shouldShowDots = false
$scope.$apply()
}
function displayPreview(contents, truncated) {
$scope.textPreview.error = false
$scope.textPreview.data = contents
$scope.textPreview.shouldShowDots = truncated
$timeout(setPreviewHeight, 0)
}
function displayPreviewError() {
$scope.textPreview.error = true
$scope.textPreview.loading = false
}
function setPreviewHeight() {
const $preview = $element.find('.text-preview .scroll-container')
const $footer = $element.find('.binary-file-footer')
const maxHeight = $element.height() - $footer.height() - 14 // borders + margin
$preview.css({ 'max-height': maxHeight })
// Don't show the preview until we've set the height, otherwise we jump around
$scope.textPreview.loading = false
}
}))

View file

@ -12,50 +12,46 @@
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
*/
define(['base', 'ide/colors/ColorManager'], (App, ColorManager) =>
App.controller('ChatMessageController', [
'$scope',
'ide',
function($scope, ide) {
const hslColorConfigs = {
borderSaturation:
(window.uiConfig != null
? window.uiConfig.chatMessageBorderSaturation
: undefined) || '70%',
borderLightness:
(window.uiConfig != null
? window.uiConfig.chatMessageBorderLightness
: undefined) || '70%',
bgSaturation:
(window.uiConfig != null
? window.uiConfig.chatMessageBgSaturation
: undefined) || '60%',
bgLightness:
(window.uiConfig != null
? window.uiConfig.chatMessageBgLightness
: undefined) || '97%'
}
const hue = function(user) {
if (user == null) {
return 0
} else {
return ColorManager.getHueForUserId(user.id)
}
}
$scope.getMessageStyle = user => ({
'border-color': `hsl(${hue(user)}, ${
hslColorConfigs.borderSaturation
}, ${hslColorConfigs.borderLightness})`,
'background-color': `hsl(${hue(user)}, ${
hslColorConfigs.bgSaturation
}, ${hslColorConfigs.bgLightness})`
})
return ($scope.getArrowStyle = user => ({
'border-color': `hsl(${hue(user)}, ${
hslColorConfigs.borderSaturation
}, ${hslColorConfigs.borderLightness})`
}))
App.controller('ChatMessageController', function($scope, ide) {
const hslColorConfigs = {
borderSaturation:
(window.uiConfig != null
? window.uiConfig.chatMessageBorderSaturation
: undefined) || '70%',
borderLightness:
(window.uiConfig != null
? window.uiConfig.chatMessageBorderLightness
: undefined) || '70%',
bgSaturation:
(window.uiConfig != null
? window.uiConfig.chatMessageBgSaturation
: undefined) || '60%',
bgLightness:
(window.uiConfig != null
? window.uiConfig.chatMessageBgLightness
: undefined) || '97%'
}
]))
const hue = function(user) {
if (user == null) {
return 0
} else {
return ColorManager.getHueForUserId(user.id)
}
}
$scope.getMessageStyle = user => ({
'border-color': `hsl(${hue(user)}, ${hslColorConfigs.borderSaturation}, ${
hslColorConfigs.borderLightness
})`,
'background-color': `hsl(${hue(user)}, ${hslColorConfigs.bgSaturation}, ${
hslColorConfigs.bgLightness
})`
})
return ($scope.getArrowStyle = user => ({
'border-color': `hsl(${hue(user)}, ${hslColorConfigs.borderSaturation}, ${
hslColorConfigs.borderLightness
})`
}))
}))

View file

@ -14,198 +14,194 @@
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
*/
define(['base', 'libs/jquery-layout', 'libs/jquery.ui.touch-punch'], App =>
App.directive('layout', [
'$parse',
'$compile',
'ide',
($parse, $compile, ide) => ({
compile() {
return {
pre(scope, element, attrs) {
let customTogglerEl, spacingClosed, spacingOpen, state
const name = attrs.layout
App.directive('layout', ($parse, $compile, ide) => ({
compile() {
return {
pre(scope, element, attrs) {
let customTogglerEl, spacingClosed, spacingOpen, state
const name = attrs.layout
const { customTogglerPane } = attrs
const { customTogglerMsgWhenOpen } = attrs
const { customTogglerMsgWhenClosed } = attrs
const hasCustomToggler =
customTogglerPane != null &&
customTogglerMsgWhenOpen != null &&
customTogglerMsgWhenClosed != null
const { customTogglerPane } = attrs
const { customTogglerMsgWhenOpen } = attrs
const { customTogglerMsgWhenClosed } = attrs
const hasCustomToggler =
customTogglerPane != null &&
customTogglerMsgWhenOpen != null &&
customTogglerMsgWhenClosed != null
if (attrs.spacingOpen != null) {
spacingOpen = parseInt(attrs.spacingOpen, 10)
} else {
spacingOpen = window.uiConfig.defaultResizerSizeOpen
if (attrs.spacingOpen != null) {
spacingOpen = parseInt(attrs.spacingOpen, 10)
} else {
spacingOpen = window.uiConfig.defaultResizerSizeOpen
}
if (attrs.spacingClosed != null) {
spacingClosed = parseInt(attrs.spacingClosed, 10)
} else {
spacingClosed = window.uiConfig.defaultResizerSizeClosed
}
const options = {
spacing_open: spacingOpen,
spacing_closed: spacingClosed,
slidable: false,
enableCursorHotkey: false,
onopen: pane => {
return onPaneOpen(pane)
},
onclose: pane => {
return onPaneClose(pane)
},
onresize: () => {
return onInternalResize()
},
maskIframesOnResize: scope.$eval(
attrs.maskIframesOnResize || 'false'
),
east: {
size: scope.$eval(attrs.initialSizeEast),
initClosed: scope.$eval(attrs.initClosedEast)
},
west: {
size: scope.$eval(attrs.initialSizeEast),
initClosed: scope.$eval(attrs.initClosedWest)
}
}
if (attrs.spacingClosed != null) {
spacingClosed = parseInt(attrs.spacingClosed, 10)
} else {
spacingClosed = window.uiConfig.defaultResizerSizeClosed
}
const options = {
spacing_open: spacingOpen,
spacing_closed: spacingClosed,
slidable: false,
enableCursorHotkey: false,
onopen: pane => {
return onPaneOpen(pane)
},
onclose: pane => {
return onPaneClose(pane)
},
onresize: () => {
return onInternalResize()
},
maskIframesOnResize: scope.$eval(
attrs.maskIframesOnResize || 'false'
),
east: {
size: scope.$eval(attrs.initialSizeEast),
initClosed: scope.$eval(attrs.initClosedEast)
},
west: {
size: scope.$eval(attrs.initialSizeEast),
initClosed: scope.$eval(attrs.initClosedWest)
// Restore previously recorded state
if ((state = ide.localStorage(`layout.${name}`)) != null) {
if (state.east != null) {
if (
attrs.minimumRestoreSizeEast == null ||
(state.east.size >= attrs.minimumRestoreSizeEast &&
!state.east.initClosed)
) {
options.east = state.east
}
}
// Restore previously recorded state
if ((state = ide.localStorage(`layout.${name}`)) != null) {
if (state.east != null) {
if (
attrs.minimumRestoreSizeEast == null ||
(state.east.size >= attrs.minimumRestoreSizeEast &&
!state.east.initClosed)
) {
options.east = state.east
}
}
if (state.west != null) {
if (
attrs.minimumRestoreSizeWest == null ||
(state.west.size >= attrs.minimumRestoreSizeWest &&
!state.west.initClosed)
) {
options.west = state.west
}
if (state.west != null) {
if (
attrs.minimumRestoreSizeWest == null ||
(state.west.size >= attrs.minimumRestoreSizeWest &&
!state.west.initClosed)
) {
options.west = state.west
}
}
}
if (window.uiConfig.eastResizerCursor != null) {
options.east.resizerCursor = window.uiConfig.eastResizerCursor
}
if (window.uiConfig.eastResizerCursor != null) {
options.east.resizerCursor = window.uiConfig.eastResizerCursor
}
if (window.uiConfig.westResizerCursor != null) {
options.west.resizerCursor = window.uiConfig.westResizerCursor
}
if (window.uiConfig.westResizerCursor != null) {
options.west.resizerCursor = window.uiConfig.westResizerCursor
}
const repositionControls = function() {
state = element.layout().readState()
if (state.east != null) {
const controls = element.find('> .ui-layout-resizer-controls')
if (state.east.initClosed) {
return controls.hide()
} else {
controls.show()
return controls.css({
right: state.east.size
})
}
const repositionControls = function() {
state = element.layout().readState()
if (state.east != null) {
const controls = element.find('> .ui-layout-resizer-controls')
if (state.east.initClosed) {
return controls.hide()
} else {
controls.show()
return controls.css({
right: state.east.size
})
}
}
}
const repositionCustomToggler = function() {
if (customTogglerEl == null) {
const repositionCustomToggler = function() {
if (customTogglerEl == null) {
return
}
state = element.layout().readState()
const positionAnchor =
customTogglerPane === 'east' ? 'right' : 'left'
const paneState = state[customTogglerPane]
if (paneState != null) {
return customTogglerEl.css(
positionAnchor,
paneState.initClosed ? 0 : paneState.size
)
}
}
const resetOpenStates = function() {
state = element.layout().readState()
if (attrs.openEast != null && state.east != null) {
const openEast = $parse(attrs.openEast)
return openEast.assign(scope, !state.east.initClosed)
}
}
// Someone moved the resizer
var onInternalResize = function() {
state = element.layout().readState()
scope.$broadcast(`layout:${name}:resize`, state)
repositionControls()
if (hasCustomToggler) {
repositionCustomToggler()
}
return resetOpenStates()
}
let oldWidth = element.width()
// Something resized our parent element
const onExternalResize = function() {
if (
attrs.resizeProportionally != null &&
scope.$eval(attrs.resizeProportionally)
) {
const eastState = element.layout().readState().east
if (eastState != null) {
const newInternalWidth =
(eastState.size / oldWidth) * element.width()
oldWidth = element.width()
element.layout().sizePane('east', newInternalWidth)
return
}
state = element.layout().readState()
const positionAnchor =
customTogglerPane === 'east' ? 'right' : 'left'
const paneState = state[customTogglerPane]
if (paneState != null) {
return customTogglerEl.css(
positionAnchor,
paneState.initClosed ? 0 : paneState.size
)
}
}
const resetOpenStates = function() {
state = element.layout().readState()
if (attrs.openEast != null && state.east != null) {
const openEast = $parse(attrs.openEast)
return openEast.assign(scope, !state.east.initClosed)
}
return element.layout().resizeAll()
}
element.layout(options)
element.layout().resizeAll()
if (attrs.resizeOn != null) {
for (let event of Array.from(attrs.resizeOn.split(','))) {
scope.$on(event, () => onExternalResize())
}
}
if (hasCustomToggler) {
state = element.layout().readState()
const customTogglerScope = scope.$new()
customTogglerScope.isOpen = true
customTogglerScope.isVisible = true
if (
(state[customTogglerPane] != null
? state[customTogglerPane].initClosed
: undefined) === true
) {
customTogglerScope.isOpen = false
}
// Someone moved the resizer
var onInternalResize = function() {
state = element.layout().readState()
scope.$broadcast(`layout:${name}:resize`, state)
repositionControls()
if (hasCustomToggler) {
repositionCustomToggler()
}
return resetOpenStates()
customTogglerScope.tooltipMsgWhenOpen = customTogglerMsgWhenOpen
customTogglerScope.tooltipMsgWhenClosed = customTogglerMsgWhenClosed
customTogglerScope.tooltipPlacement =
customTogglerPane === 'east' ? 'left' : 'right'
customTogglerScope.handleClick = function() {
element.layout().toggle(customTogglerPane)
return repositionCustomToggler()
}
let oldWidth = element.width()
// Something resized our parent element
const onExternalResize = function() {
if (
attrs.resizeProportionally != null &&
scope.$eval(attrs.resizeProportionally)
) {
const eastState = element.layout().readState().east
if (eastState != null) {
const newInternalWidth =
(eastState.size / oldWidth) * element.width()
oldWidth = element.width()
element.layout().sizePane('east', newInternalWidth)
return
}
}
return element.layout().resizeAll()
}
element.layout(options)
element.layout().resizeAll()
if (attrs.resizeOn != null) {
for (let event of Array.from(attrs.resizeOn.split(','))) {
scope.$on(event, () => onExternalResize())
}
}
if (hasCustomToggler) {
state = element.layout().readState()
const customTogglerScope = scope.$new()
customTogglerScope.isOpen = true
customTogglerScope.isVisible = true
if (
(state[customTogglerPane] != null
? state[customTogglerPane].initClosed
: undefined) === true
) {
customTogglerScope.isOpen = false
}
customTogglerScope.tooltipMsgWhenOpen = customTogglerMsgWhenOpen
customTogglerScope.tooltipMsgWhenClosed = customTogglerMsgWhenClosed
customTogglerScope.tooltipPlacement =
customTogglerPane === 'east' ? 'left' : 'right'
customTogglerScope.handleClick = function() {
element.layout().toggle(customTogglerPane)
return repositionCustomToggler()
}
customTogglerEl = $compile(`\
customTogglerEl = $compile(`\
<a href \
ng-show=\"isVisible\" \
class=\"custom-toggler ${`custom-toggler-${customTogglerPane}`}\" \
@ -214,82 +210,81 @@ tooltip=\"{{ isOpen ? tooltipMsgWhenOpen : tooltipMsgWhenClosed }}\" \
tooltip-placement=\"{{ tooltipPlacement }}\" \
ng-click=\"handleClick()\">\
`)(customTogglerScope)
element.append(customTogglerEl)
}
var onPaneOpen = function(pane) {
if (!hasCustomToggler && pane !== customTogglerPane) {
return
}
return customTogglerEl
.scope()
.$applyAsync(() => (customTogglerEl.scope().isOpen = true))
}
var onPaneClose = function(pane) {
if (!hasCustomToggler && pane !== customTogglerPane) {
return
}
return customTogglerEl
.scope()
.$applyAsync(() => (customTogglerEl.scope().isOpen = false))
}
// Save state when exiting
$(window).unload(() =>
ide.localStorage(`layout.${name}`, element.layout().readState())
)
if (attrs.openEast != null) {
scope.$watch(attrs.openEast, function(value, oldValue) {
if (value != null && value !== oldValue) {
if (value) {
element.layout().open('east')
} else {
element.layout().close('east')
}
}
return setTimeout(() => scope.$digest(), 0)
})
}
if (attrs.allowOverflowOn != null) {
const layoutObj = element.layout()
const overflowPane = scope.$eval(attrs.allowOverflowOn)
const overflowPaneEl = layoutObj.panes[overflowPane]
// Set the panel as overflowing (gives it higher z-index and sets overflow rules)
layoutObj.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) {
element.layout().hide('east')
} else {
element.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)
element.append(customTogglerEl)
}
var onPaneOpen = function(pane) {
if (!hasCustomToggler && pane !== customTogglerPane) {
return
}
return customTogglerEl
.scope()
.$applyAsync(() => (customTogglerEl.scope().isOpen = true))
}
var onPaneClose = function(pane) {
if (!hasCustomToggler && pane !== customTogglerPane) {
return
}
return customTogglerEl
.scope()
.$applyAsync(() => (customTogglerEl.scope().isOpen = false))
}
// Save state when exiting
$(window).unload(() =>
ide.localStorage(`layout.${name}`, element.layout().readState())
)
if (attrs.openEast != null) {
scope.$watch(attrs.openEast, function(value, oldValue) {
if (value != null && value !== oldValue) {
if (value) {
element.layout().open('east')
} else {
element.layout().close('east')
}
}
return setTimeout(() => scope.$digest(), 0)
})
}
if (attrs.allowOverflowOn != null) {
const layoutObj = element.layout()
const overflowPane = scope.$eval(attrs.allowOverflowOn)
const overflowPaneEl = layoutObj.panes[overflowPane]
// Set the panel as overflowing (gives it higher z-index and sets overflow rules)
layoutObj.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) {
element.layout().hide('east')
} else {
element.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)
}
}
})
]))
}
})))

View file

@ -13,81 +13,76 @@
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
*/
define(['base', 'ide/editor/Document'], (App, Document) =>
App.controller('SavingNotificationController', [
'$scope',
'$interval',
'ide',
function($scope, $interval, ide) {
let warnAboutUnsavedChanges
setInterval(() => pollSavedStatus(), 1000)
App.controller('SavingNotificationController', function(
$scope,
$interval,
ide
) {
let warnAboutUnsavedChanges
setInterval(() => pollSavedStatus(), 1000)
$(window).bind('beforeunload', () => {
return warnAboutUnsavedChanges()
})
$(window).bind('beforeunload', () => {
return warnAboutUnsavedChanges()
})
let lockEditorModal = null // modal showing "connection lost"
const MAX_UNSAVED_SECONDS = 15 // lock the editor after this time if unsaved
let lockEditorModal = null // modal showing "connection lost"
const MAX_UNSAVED_SECONDS = 15 // lock the editor after this time if unsaved
$scope.docSavingStatus = {}
var pollSavedStatus = function() {
let t
const oldStatus = $scope.docSavingStatus
const oldUnsavedCount = $scope.docSavingStatusCount
const newStatus = {}
let newUnsavedCount = 0
let maxUnsavedSeconds = 0
$scope.docSavingStatus = {}
var pollSavedStatus = function() {
let t
const oldStatus = $scope.docSavingStatus
const oldUnsavedCount = $scope.docSavingStatusCount
const newStatus = {}
let newUnsavedCount = 0
let maxUnsavedSeconds = 0
for (let doc_id in Document.openDocs) {
const doc = Document.openDocs[doc_id]
const saving = doc.pollSavedStatus()
if (!saving) {
newUnsavedCount++
if (oldStatus[doc_id] != null) {
newStatus[doc_id] = oldStatus[doc_id]
t = newStatus[doc_id].unsavedSeconds += 1
if (t > maxUnsavedSeconds) {
maxUnsavedSeconds = t
}
} else {
newStatus[doc_id] = {
unsavedSeconds: 0,
doc: ide.fileTreeManager.findEntityById(doc_id)
}
for (let doc_id in Document.openDocs) {
const doc = Document.openDocs[doc_id]
const saving = doc.pollSavedStatus()
if (!saving) {
newUnsavedCount++
if (oldStatus[doc_id] != null) {
newStatus[doc_id] = oldStatus[doc_id]
t = newStatus[doc_id].unsavedSeconds += 1
if (t > maxUnsavedSeconds) {
maxUnsavedSeconds = t
}
} else {
newStatus[doc_id] = {
unsavedSeconds: 0,
doc: ide.fileTreeManager.findEntityById(doc_id)
}
}
}
if (
newUnsavedCount > 0 &&
t > MAX_UNSAVED_SECONDS &&
!lockEditorModal
) {
lockEditorModal = ide.showLockEditorMessageModal(
'Connection lost',
'Sorry, the connection to the server is down.'
)
lockEditorModal.result.finally(() => (lockEditorModal = null)) // unset the modal if connection comes back
}
if (lockEditorModal && newUnsavedCount === 0) {
lockEditorModal.dismiss('connection back up')
}
// for performance, only update the display if the old or new
// counts of unsaved files are nonzeror. If both old and new
// unsaved counts are zero then we know we are in a good state
// and don't need to do anything to the UI.
if (newUnsavedCount || oldUnsavedCount) {
$scope.docSavingStatus = newStatus
$scope.docSavingStatusCount = newUnsavedCount
return $scope.$apply()
}
}
return (warnAboutUnsavedChanges = function() {
if (Document.hasUnsavedChanges()) {
return 'You have unsaved changes. If you leave now they will not be saved.'
}
})
if (newUnsavedCount > 0 && t > MAX_UNSAVED_SECONDS && !lockEditorModal) {
lockEditorModal = ide.showLockEditorMessageModal(
'Connection lost',
'Sorry, the connection to the server is down.'
)
lockEditorModal.result.finally(() => (lockEditorModal = null)) // unset the modal if connection comes back
}
if (lockEditorModal && newUnsavedCount === 0) {
lockEditorModal.dismiss('connection back up')
}
// for performance, only update the display if the old or new
// counts of unsaved files are nonzeror. If both old and new
// unsaved counts are zero then we know we are in a good state
// and don't need to do anything to the UI.
if (newUnsavedCount || oldUnsavedCount) {
$scope.docSavingStatus = newStatus
$scope.docSavingStatusCount = newUnsavedCount
return $scope.$apply()
}
}
]))
return (warnAboutUnsavedChanges = function() {
if (Document.hasUnsavedChanges()) {
return 'You have unsaved changes. If you leave now they will not be saved.'
}
})
}))

View file

@ -17,125 +17,119 @@ define(['base', 'ide/file-tree/util/iconTypeFromName'], function(
App,
iconTypeFromName
) {
App.controller('FileTreeEntityController', [
'$scope',
'ide',
'$modal',
function($scope, ide, $modal) {
$scope.select = function(e) {
if (e.ctrlKey || e.metaKey) {
e.stopPropagation()
const initialMultiSelectCount = ide.fileTreeManager.multiSelectedCount()
ide.fileTreeManager.toggleMultiSelectEntity($scope.entity) === 0
if (initialMultiSelectCount === 0) {
// On first multi selection, also include the current active/open file.
return ide.fileTreeManager.multiSelectSelectedEntity()
}
} else {
ide.fileTreeManager.selectEntity($scope.entity)
return $scope.$emit('entity:selected', $scope.entity)
App.controller('FileTreeEntityController', function($scope, ide, $modal) {
$scope.select = function(e) {
if (e.ctrlKey || e.metaKey) {
e.stopPropagation()
const initialMultiSelectCount = ide.fileTreeManager.multiSelectedCount()
ide.fileTreeManager.toggleMultiSelectEntity($scope.entity) === 0
if (initialMultiSelectCount === 0) {
// On first multi selection, also include the current active/open file.
return ide.fileTreeManager.multiSelectSelectedEntity()
}
} else {
ide.fileTreeManager.selectEntity($scope.entity)
return $scope.$emit('entity:selected', $scope.entity)
}
}
$scope.draggableHelper = function() {
if (ide.fileTreeManager.multiSelectedCount() > 0) {
return $(
`<strong style='z-index:100'>${ide.fileTreeManager.multiSelectedCount()} Files</strong>`
)
} else {
return $(`<strong style='z-index:100'>${$scope.entity.name}</strong>`)
}
}
$scope.inputs = { name: $scope.entity.name }
$scope.startRenaming = () => ($scope.entity.renaming = true)
let invalidModalShowing = false
$scope.finishRenaming = function() {
// avoid double events when blur and on-enter fire together
if (!$scope.entity.renaming) {
return
}
$scope.draggableHelper = function() {
if (ide.fileTreeManager.multiSelectedCount() > 0) {
return $(
`<strong style='z-index:100'>${ide.fileTreeManager.multiSelectedCount()} Files</strong>`
)
} else {
return $(`<strong style='z-index:100'>${$scope.entity.name}</strong>`)
}
}
const { name } = $scope.inputs
$scope.inputs = { name: $scope.entity.name }
$scope.startRenaming = () => ($scope.entity.renaming = true)
let invalidModalShowing = false
$scope.finishRenaming = function() {
// avoid double events when blur and on-enter fire together
if (!$scope.entity.renaming) {
// validator will set name to undefined for invalid filenames
if (name == null) {
// Showing the modal blurs the rename box which calls us again
// so track this with the invalidModalShowing flag
if (invalidModalShowing) {
return
}
const { name } = $scope.inputs
// validator will set name to undefined for invalid filenames
if (name == null) {
// Showing the modal blurs the rename box which calls us again
// so track this with the invalidModalShowing flag
if (invalidModalShowing) {
return
}
invalidModalShowing = true
const modal = $modal.open({
templateUrl: 'invalidFileNameModalTemplate'
})
modal.result.then(() => (invalidModalShowing = false))
return
}
delete $scope.entity.renaming
if (name == null || name.length === 0) {
$scope.inputs.name = $scope.entity.name
return
}
return ide.fileTreeManager.renameEntity($scope.entity, name)
}
$scope.$on('rename:selected', function() {
if ($scope.entity.selected) {
return $scope.startRenaming()
}
})
$scope.openDeleteModal = function() {
let entities
if (ide.fileTreeManager.multiSelectedCount() > 0) {
entities = ide.fileTreeManager.getMultiSelectedEntityChildNodes()
} else {
entities = [$scope.entity]
}
return $modal.open({
templateUrl: 'deleteEntityModalTemplate',
controller: 'DeleteEntityModalController',
resolve: {
entities() {
return entities
}
}
invalidModalShowing = true
const modal = $modal.open({
templateUrl: 'invalidFileNameModalTemplate'
})
modal.result.then(() => (invalidModalShowing = false))
return
}
$scope.$on('delete:selected', function() {
if ($scope.entity.selected) {
return $scope.openDeleteModal()
delete $scope.entity.renaming
if (name == null || name.length === 0) {
$scope.inputs.name = $scope.entity.name
return
}
return ide.fileTreeManager.renameEntity($scope.entity, name)
}
$scope.$on('rename:selected', function() {
if ($scope.entity.selected) {
return $scope.startRenaming()
}
})
$scope.openDeleteModal = function() {
let entities
if (ide.fileTreeManager.multiSelectedCount() > 0) {
entities = ide.fileTreeManager.getMultiSelectedEntityChildNodes()
} else {
entities = [$scope.entity]
}
return $modal.open({
templateUrl: 'deleteEntityModalTemplate',
controller: 'DeleteEntityModalController',
resolve: {
entities() {
return entities
}
}
})
return ($scope.iconTypeFromName = iconTypeFromName)
}
])
return App.controller('DeleteEntityModalController', [
'$scope',
'ide',
'$modalInstance',
'entities',
function($scope, ide, $modalInstance, entities) {
$scope.state = { inflight: false }
$scope.entities = entities
$scope.delete = function() {
$scope.state.inflight = true
for (let entity of Array.from($scope.entities)) {
ide.fileTreeManager.deleteEntity(entity)
}
return $modalInstance.close()
$scope.$on('delete:selected', function() {
if ($scope.entity.selected) {
return $scope.openDeleteModal()
}
})
return ($scope.cancel = () => $modalInstance.dismiss('cancel'))
return ($scope.iconTypeFromName = iconTypeFromName)
})
return App.controller('DeleteEntityModalController', function(
$scope,
ide,
$modalInstance,
entities
) {
$scope.state = { inflight: false }
$scope.entities = entities
$scope.delete = function() {
$scope.state.inflight = true
for (let entity of Array.from($scope.entities)) {
ide.fileTreeManager.deleteEntity(entity)
}
return $modalInstance.close()
}
])
return ($scope.cancel = () => $modalInstance.dismiss('cancel'))
})
})

View file

@ -13,26 +13,22 @@
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
*/
define(['base'], App =>
App.controller('FileTreeRootFolderController', [
'$scope',
'ide',
function($scope, ide) {
const { rootFolder } = $scope
return ($scope.onDrop = function(events, ui) {
let entities
if (ide.fileTreeManager.multiSelectedCount()) {
entities = ide.fileTreeManager.getMultiSelectedEntityChildNodes()
} else {
entities = [$(ui.draggable).scope().entity]
}
for (let dropped_entity of Array.from(entities)) {
ide.fileTreeManager.moveEntity(dropped_entity, rootFolder)
}
$scope.$digest()
// clear highlight explicitly
return $('.file-tree-inner .droppable-hover').removeClass(
'droppable-hover'
)
})
}
]))
App.controller('FileTreeRootFolderController', function($scope, ide) {
const { rootFolder } = $scope
return ($scope.onDrop = function(events, ui) {
let entities
if (ide.fileTreeManager.multiSelectedCount()) {
entities = ide.fileTreeManager.getMultiSelectedEntityChildNodes()
} else {
entities = [$(ui.draggable).scope().entity]
}
for (let dropped_entity of Array.from(entities)) {
ide.fileTreeManager.moveEntity(dropped_entity, rootFolder)
}
$scope.$digest()
// clear highlight explicitly
return $('.file-tree-inner .droppable-hover').removeClass(
'droppable-hover'
)
})
}))

View file

@ -12,36 +12,33 @@
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
*/
define(['base'], App =>
App.directive('fileEntity', [
'RecursionHelper',
RecursionHelper => ({
restrict: 'E',
scope: {
entity: '=',
permissions: '='
},
templateUrl: 'entityListItemTemplate',
compile(element) {
return RecursionHelper.compile(element, function(
scope,
element,
attrs,
ctrl
) {
// Don't freak out if we're already in an apply callback
scope.$originalApply = scope.$apply
return (scope.$apply = function(fn) {
if (fn == null) {
fn = function() {}
}
const phase = this.$root.$$phase
if (phase === '$apply' || phase === '$digest') {
return fn()
} else {
return this.$originalApply(fn)
}
})
App.directive('fileEntity', RecursionHelper => ({
restrict: 'E',
scope: {
entity: '=',
permissions: '='
},
templateUrl: 'entityListItemTemplate',
compile(element) {
return RecursionHelper.compile(element, function(
scope,
element,
attrs,
ctrl
) {
// Don't freak out if we're already in an apply callback
scope.$originalApply = scope.$apply
return (scope.$apply = function(fn) {
if (fn == null) {
fn = function() {}
}
const phase = this.$root.$$phase
if (phase === '$apply' || phase === '$digest') {
return fn()
} else {
return this.$originalApply(fn)
}
})
}
})
]))
})
}
})))

View file

@ -18,73 +18,118 @@ define(['base', 'ide/history/util/displayNameForUser'], function(
App,
displayNameForUser
) {
App.controller('HistoryListController', [
'$scope',
'$modal',
'ide',
function($scope, $modal, ide) {
$scope.hoveringOverListSelectors = false
App.controller('HistoryListController', function($scope, $modal, ide) {
$scope.hoveringOverListSelectors = false
$scope.projectUsers = []
$scope.projectUsers = []
$scope.$watch('project.members', function(newVal) {
if (newVal != null) {
return ($scope.projectUsers = newVal.concat($scope.project.owner))
$scope.$watch('project.members', function(newVal) {
if (newVal != null) {
return ($scope.projectUsers = newVal.concat($scope.project.owner))
}
})
// This method (and maybe the one below) will be removed soon. User details data will be
// injected into the history API responses, so we won't need to fetch user data from other
// local data structures.
const _getUserById = id =>
_.find($scope.projectUsers, function(user) {
const curUserId =
(user != null ? user._id : undefined) ||
(user != null ? user.id : undefined)
return curUserId === id
})
$scope.getDisplayNameById = id => displayNameForUser(_getUserById(id))
$scope.deleteLabel = labelDetails =>
$modal.open({
templateUrl: 'historyV2DeleteLabelModalTemplate',
controller: 'HistoryV2DeleteLabelModalController',
resolve: {
labelDetails() {
return labelDetails
}
}
})
// This method (and maybe the one below) will be removed soon. User details data will be
// injected into the history API responses, so we won't need to fetch user data from other
// local data structures.
const _getUserById = id =>
_.find($scope.projectUsers, function(user) {
const curUserId =
(user != null ? user._id : undefined) ||
(user != null ? user.id : undefined)
return curUserId === id
})
$scope.loadMore = () => {
return ide.historyManager.fetchNextBatchOfUpdates()
}
$scope.getDisplayNameById = id => displayNameForUser(_getUserById(id))
$scope.deleteLabel = labelDetails =>
$modal.open({
templateUrl: 'historyV2DeleteLabelModalTemplate',
controller: 'HistoryV2DeleteLabelModalController',
resolve: {
labelDetails() {
return labelDetails
}
$scope.recalculateSelectedUpdates = function() {
let beforeSelection = true
let afterSelection = false
$scope.history.selection.updates = []
return (() => {
const result = []
for (let update of Array.from($scope.history.updates)) {
var inSelection
if (update.selectedTo) {
inSelection = true
beforeSelection = false
}
})
$scope.loadMore = () => {
return ide.historyManager.fetchNextBatchOfUpdates()
update.beforeSelection = beforeSelection
update.inSelection = inSelection
update.afterSelection = afterSelection
if (inSelection) {
$scope.history.selection.updates.push(update)
}
if (update.selectedFrom) {
inSelection = false
result.push((afterSelection = true))
} else {
result.push(undefined)
}
}
return result
})()
}
$scope.recalculateHoveredUpdates = function() {
let inHoverSelection
let hoverSelectedFrom = false
let hoverSelectedTo = false
for (var update of Array.from($scope.history.updates)) {
// Figure out whether the to or from selector is hovered over
if (update.hoverSelectedFrom) {
hoverSelectedFrom = true
}
if (update.hoverSelectedTo) {
hoverSelectedTo = true
}
}
$scope.recalculateSelectedUpdates = function() {
let beforeSelection = true
let afterSelection = false
$scope.history.selection.updates = []
if (hoverSelectedFrom) {
// We want to 'hover select' everything between hoverSelectedFrom and selectedTo
inHoverSelection = false
for (update of Array.from($scope.history.updates)) {
if (update.selectedTo) {
update.hoverSelectedTo = true
inHoverSelection = true
}
update.inHoverSelection = inHoverSelection
if (update.hoverSelectedFrom) {
inHoverSelection = false
}
}
}
if (hoverSelectedTo) {
// We want to 'hover select' everything between hoverSelectedTo and selectedFrom
inHoverSelection = false
return (() => {
const result = []
for (let update of Array.from($scope.history.updates)) {
var inSelection
if (update.selectedTo) {
inSelection = true
beforeSelection = false
for (update of Array.from($scope.history.updates)) {
if (update.hoverSelectedTo) {
inHoverSelection = true
}
update.beforeSelection = beforeSelection
update.inSelection = inSelection
update.afterSelection = afterSelection
if (inSelection) {
$scope.history.selection.updates.push(update)
}
update.inHoverSelection = inHoverSelection
if (update.selectedFrom) {
inSelection = false
result.push((afterSelection = true))
update.hoverSelectedFrom = true
result.push((inHoverSelection = false))
} else {
result.push(undefined)
}
@ -92,132 +137,81 @@ define(['base', 'ide/history/util/displayNameForUser'], function(
return result
})()
}
$scope.recalculateHoveredUpdates = function() {
let inHoverSelection
let hoverSelectedFrom = false
let hoverSelectedTo = false
for (var update of Array.from($scope.history.updates)) {
// Figure out whether the to or from selector is hovered over
if (update.hoverSelectedFrom) {
hoverSelectedFrom = true
}
if (update.hoverSelectedTo) {
hoverSelectedTo = true
}
}
if (hoverSelectedFrom) {
// We want to 'hover select' everything between hoverSelectedFrom and selectedTo
inHoverSelection = false
for (update of Array.from($scope.history.updates)) {
if (update.selectedTo) {
update.hoverSelectedTo = true
inHoverSelection = true
}
update.inHoverSelection = inHoverSelection
if (update.hoverSelectedFrom) {
inHoverSelection = false
}
}
}
if (hoverSelectedTo) {
// We want to 'hover select' everything between hoverSelectedTo and selectedFrom
inHoverSelection = false
return (() => {
const result = []
for (update of Array.from($scope.history.updates)) {
if (update.hoverSelectedTo) {
inHoverSelection = true
}
update.inHoverSelection = inHoverSelection
if (update.selectedFrom) {
update.hoverSelectedFrom = true
result.push((inHoverSelection = false))
} else {
result.push(undefined)
}
}
return result
})()
}
}
$scope.resetHoverState = () =>
(() => {
const result = []
for (let update of Array.from($scope.history.updates)) {
delete update.hoverSelectedFrom
delete update.hoverSelectedTo
result.push(delete update.inHoverSelection)
}
return result
})()
return $scope.$watch('history.updates.length', () =>
$scope.recalculateSelectedUpdates()
)
}
])
return App.controller('HistoryListItemController', [
'$scope',
'event_tracking',
function($scope, event_tracking) {
$scope.$watch('update.selectedFrom', function(
selectedFrom,
oldSelectedFrom
) {
if (selectedFrom) {
for (let update of Array.from($scope.history.updates)) {
if (update !== $scope.update) {
update.selectedFrom = false
}
}
return $scope.recalculateSelectedUpdates()
$scope.resetHoverState = () =>
(() => {
const result = []
for (let update of Array.from($scope.history.updates)) {
delete update.hoverSelectedFrom
delete update.hoverSelectedTo
result.push(delete update.inHoverSelection)
}
})
return result
})()
$scope.$watch('update.selectedTo', function(selectedTo, oldSelectedTo) {
if (selectedTo) {
for (let update of Array.from($scope.history.updates)) {
if (update !== $scope.update) {
update.selectedTo = false
}
return $scope.$watch('history.updates.length', () =>
$scope.recalculateSelectedUpdates()
)
})
return App.controller('HistoryListItemController', function(
$scope,
event_tracking
) {
$scope.$watch('update.selectedFrom', function(
selectedFrom,
oldSelectedFrom
) {
if (selectedFrom) {
for (let update of Array.from($scope.history.updates)) {
if (update !== $scope.update) {
update.selectedFrom = false
}
return $scope.recalculateSelectedUpdates()
}
})
$scope.select = function() {
event_tracking.sendMB('history-view-change')
$scope.update.selectedTo = true
return ($scope.update.selectedFrom = true)
return $scope.recalculateSelectedUpdates()
}
})
$scope.mouseOverSelectedFrom = function() {
$scope.history.hoveringOverListSelectors = true
$scope.update.hoverSelectedFrom = true
return $scope.recalculateHoveredUpdates()
$scope.$watch('update.selectedTo', function(selectedTo, oldSelectedTo) {
if (selectedTo) {
for (let update of Array.from($scope.history.updates)) {
if (update !== $scope.update) {
update.selectedTo = false
}
}
return $scope.recalculateSelectedUpdates()
}
})
$scope.mouseOutSelectedFrom = function() {
$scope.history.hoveringOverListSelectors = false
return $scope.resetHoverState()
}
$scope.mouseOverSelectedTo = function() {
$scope.history.hoveringOverListSelectors = true
$scope.update.hoverSelectedTo = true
return $scope.recalculateHoveredUpdates()
}
$scope.mouseOutSelectedTo = function() {
$scope.history.hoveringOverListSelectors = false
return $scope.resetHoverState()
}
return ($scope.displayName = displayNameForUser)
$scope.select = function() {
event_tracking.sendMB('history-view-change')
$scope.update.selectedTo = true
return ($scope.update.selectedFrom = true)
}
])
$scope.mouseOverSelectedFrom = function() {
$scope.history.hoveringOverListSelectors = true
$scope.update.hoverSelectedFrom = true
return $scope.recalculateHoveredUpdates()
}
$scope.mouseOutSelectedFrom = function() {
$scope.history.hoveringOverListSelectors = false
return $scope.resetHoverState()
}
$scope.mouseOverSelectedTo = function() {
$scope.history.hoveringOverListSelectors = true
$scope.update.hoverSelectedTo = true
return $scope.recalculateHoveredUpdates()
}
$scope.mouseOutSelectedTo = function() {
$scope.history.hoveringOverListSelectors = false
return $scope.resetHoverState()
}
return ($scope.displayName = displayNameForUser)
})
})

View file

@ -11,40 +11,39 @@
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
*/
define(['base'], App =>
App.controller('HistoryV2AddLabelModalController', [
'$scope',
'$modalInstance',
'ide',
'update',
function($scope, $modalInstance, ide, update) {
$scope.update = update
$scope.inputs = { labelName: null }
$scope.state = {
inflight: false,
error: false
}
$modalInstance.opened.then(() =>
$scope.$applyAsync(() => $scope.$broadcast('open'))
)
return ($scope.addLabelModalFormSubmit = function() {
$scope.state.inflight = true
return ide.historyManager
.labelCurrentVersion($scope.inputs.labelName)
.then(function(response) {
$scope.state.inflight = false
return $modalInstance.close()
})
.catch(function(response) {
const { data, status } = response
$scope.state.inflight = false
if (status === 400) {
return ($scope.state.error = { message: data })
} else {
return ($scope.state.error = true)
}
})
})
App.controller('HistoryV2AddLabelModalController', function(
$scope,
$modalInstance,
ide,
update
) {
$scope.update = update
$scope.inputs = { labelName: null }
$scope.state = {
inflight: false,
error: false
}
]))
$modalInstance.opened.then(() =>
$scope.$applyAsync(() => $scope.$broadcast('open'))
)
return ($scope.addLabelModalFormSubmit = function() {
$scope.state.inflight = true
return ide.historyManager
.labelCurrentVersion($scope.inputs.labelName)
.then(function(response) {
$scope.state.inflight = false
return $modalInstance.close()
})
.catch(function(response) {
const { data, status } = response
$scope.state.inflight = false
if (status === 400) {
return ($scope.state.error = { message: data })
} else {
return ($scope.state.error = true)
}
})
})
}))

View file

@ -11,35 +11,34 @@
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
*/
define(['base'], App =>
App.controller('HistoryV2DeleteLabelModalController', [
'$scope',
'$modalInstance',
'ide',
'labelDetails',
function($scope, $modalInstance, ide, labelDetails) {
$scope.labelDetails = labelDetails
$scope.state = {
inflight: false,
error: false
}
return ($scope.deleteLabel = function() {
$scope.state.inflight = true
return ide.historyManager
.deleteLabel(labelDetails)
.then(function(response) {
$scope.state.inflight = false
return $modalInstance.close()
})
.catch(function(response) {
const { data, status } = response
$scope.state.inflight = false
if (status === 400) {
return ($scope.state.error = { message: data })
} else {
return ($scope.state.error = true)
}
})
})
App.controller('HistoryV2DeleteLabelModalController', function(
$scope,
$modalInstance,
ide,
labelDetails
) {
$scope.labelDetails = labelDetails
$scope.state = {
inflight: false,
error: false
}
]))
return ($scope.deleteLabel = function() {
$scope.state.inflight = true
return ide.historyManager
.deleteLabel(labelDetails)
.then(function(response) {
$scope.state.inflight = false
return $modalInstance.close()
})
.catch(function(response) {
const { data, status } = response
$scope.state.inflight = false
if (status === 400) {
return ($scope.state.error = { message: data })
} else {
return ($scope.state.error = true)
}
})
})
}))

View file

@ -12,12 +12,8 @@
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
*/
define(['base'], App =>
App.controller('HistoryV2FileTreeController', [
'$scope',
'ide',
function($scope, ide) {
$scope.handleFileSelection = file => {
ide.historyManager.selectFile(file)
}
App.controller('HistoryV2FileTreeController', function($scope, ide) {
$scope.handleFileSelection = file => {
ide.historyManager.selectFile(file)
}
]))
}))

View file

@ -15,48 +15,40 @@ define(['base', 'ide/history/util/displayNameForUser'], (
App,
displayNameForUser
) =>
App.controller('HistoryV2ListController', [
'$scope',
'$modal',
'ide',
function($scope, $modal, ide) {
$scope.hoveringOverListSelectors = false
$scope.listConfig = { showOnlyLabelled: false }
App.controller('HistoryV2ListController', function($scope, $modal, ide) {
$scope.hoveringOverListSelectors = false
$scope.listConfig = { showOnlyLabelled: false }
$scope.projectUsers = []
$scope.projectUsers = []
$scope.$watch('project.members', function(newVal) {
if (newVal != null) {
return ($scope.projectUsers = newVal.concat($scope.project.owner))
}
})
$scope.loadMore = () => {
return ide.historyManager.fetchNextBatchOfUpdates()
$scope.$watch('project.members', function(newVal) {
if (newVal != null) {
return ($scope.projectUsers = newVal.concat($scope.project.owner))
}
})
$scope.handleVersionSelect = version =>
$scope.$applyAsync(() =>
ide.historyManager.selectVersionForPointInTime(version)
)
$scope.handleRangeSelect = (selectedToV, selectedFromV) =>
$scope.$applyAsync(() =>
ide.historyManager.selectVersionsForCompare(
selectedToV,
selectedFromV
)
)
return ($scope.handleLabelDelete = labelDetails =>
$modal.open({
templateUrl: 'historyV2DeleteLabelModalTemplate',
controller: 'HistoryV2DeleteLabelModalController',
resolve: {
labelDetails() {
return labelDetails
}
}
}))
$scope.loadMore = () => {
return ide.historyManager.fetchNextBatchOfUpdates()
}
]))
$scope.handleVersionSelect = version =>
$scope.$applyAsync(() =>
ide.historyManager.selectVersionForPointInTime(version)
)
$scope.handleRangeSelect = (selectedToV, selectedFromV) =>
$scope.$applyAsync(() =>
ide.historyManager.selectVersionsForCompare(selectedToV, selectedFromV)
)
return ($scope.handleLabelDelete = labelDetails =>
$modal.open({
templateUrl: 'historyV2DeleteLabelModalTemplate',
controller: 'HistoryV2DeleteLabelModalController',
resolve: {
labelDetails() {
return labelDetails
}
}
}))
}))

View file

@ -12,12 +12,8 @@
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
*/
define(['base'], App =>
App.controller('HistoryV2ToolbarController', [
'$scope',
'$modal',
'ide',
'event_tracking',
'waitFor',
App.controller(
'HistoryV2ToolbarController',
($scope, $modal, ide, event_tracking, waitFor) => {
let openEntity
@ -126,4 +122,4 @@ define(['base'], App =>
.catch(err => console.warn(err))
}
}
]))
))

View file

@ -30,7 +30,7 @@ define([
// and then again on ack.
const AUTO_COMPILE_DEBOUNCE = 2000
App.filter('trusted', ['$sce', $sce => url => $sce.trustAsResourceUrl(url)])
App.filter('trusted', $sce => url => $sce.trustAsResourceUrl(url))
App.controller('PdfController', function(
$scope,
@ -898,188 +898,175 @@ define([
}))
})
App.factory('synctex', [
'ide',
'$http',
'$q',
function(ide, $http, $q) {
// enable per-user containers by default
const perUserCompile = true
App.factory('synctex', function(ide, $http, $q) {
// enable per-user containers by default
const perUserCompile = true
const synctex = {
syncToPdf(cursorPosition) {
const deferred = $q.defer()
const doc_id = ide.editorManager.getCurrentDocId()
if (doc_id == null) {
deferred.reject()
return deferred.promise
}
const doc = ide.fileTreeManager.findEntityById(doc_id)
if (doc == null) {
deferred.reject()
return deferred.promise
}
let path = ide.fileTreeManager.getEntityPath(doc)
if (path == null) {
deferred.reject()
return deferred.promise
}
// If the root file is folder/main.tex, then synctex sees the
// path as folder/./main.tex
const rootDocDirname = ide.fileTreeManager.getRootDocDirname()
if (rootDocDirname != null && rootDocDirname !== '') {
path = path.replace(
RegExp(`^${rootDocDirname}`),
`${rootDocDirname}/.`
)
}
const { row, column } = cursorPosition
$http({
url: `/project/${ide.project_id}/sync/code`,
method: 'GET',
params: {
file: path,
line: row + 1,
column,
clsiserverid: ide.clsiServerId
}
})
.then(function(response) {
const { data } = response
return deferred.resolve(data.pdf || [])
})
.catch(function(response) {
const error = response.data
return deferred.reject(error)
})
const synctex = {
syncToPdf(cursorPosition) {
const deferred = $q.defer()
const doc_id = ide.editorManager.getCurrentDocId()
if (doc_id == null) {
deferred.reject()
return deferred.promise
},
}
const doc = ide.fileTreeManager.findEntityById(doc_id)
if (doc == null) {
deferred.reject()
return deferred.promise
}
let path = ide.fileTreeManager.getEntityPath(doc)
if (path == null) {
deferred.reject()
return deferred.promise
}
syncToCode(position, options) {
let v
if (options == null) {
options = {}
// If the root file is folder/main.tex, then synctex sees the
// path as folder/./main.tex
const rootDocDirname = ide.fileTreeManager.getRootDocDirname()
if (rootDocDirname != null && rootDocDirname !== '') {
path = path.replace(
RegExp(`^${rootDocDirname}`),
`${rootDocDirname}/.`
)
}
const { row, column } = cursorPosition
$http({
url: `/project/${ide.project_id}/sync/code`,
method: 'GET',
params: {
file: path,
line: row + 1,
column,
clsiserverid: ide.clsiServerId
}
const deferred = $q.defer()
if (position == null) {
deferred.reject()
return deferred.promise
}
// FIXME: this actually works better if it's halfway across the
// page (or the visible part of the page). Synctex doesn't
// always find the right place in the file when the point is at
// the edge of the page, it sometimes returns the start of the
// next paragraph instead.
const h = position.offset.left
// Compute the vertical position to pass to synctex, which
// works with coordinates increasing from the top of the page
// down. This matches the browser's DOM coordinate of the
// click point, but the pdf position is measured from the
// bottom of the page so we need to invert it.
if (
options.fromPdfPosition &&
(position.pageSize != null
? position.pageSize.height
: undefined) != null
) {
v = position.pageSize.height - position.offset.top || 0 // measure from pdf point (inverted)
} else {
v = position.offset.top || 0 // measure from html click position
}
// It's not clear exactly where we should sync to if it wasn't directly
// clicked on, but a little bit down from the very top seems best.
if (options.includeVisualOffset) {
v += 72 // use the same value as in pdfViewer highlighting visual offset
}
$http({
url: `/project/${ide.project_id}/sync/pdf`,
method: 'GET',
params: {
page: position.page + 1,
h: h.toFixed(2),
v: v.toFixed(2),
clsiserverid: ide.clsiServerId
}
})
.then(function(response) {
const { data } = response
return deferred.resolve(data.pdf || [])
})
.then(function(response) {
const { data } = response
if (
data.code != null &&
data.code.length > 0 &&
data.code[0].file !== ''
) {
const doc = ide.fileTreeManager.findEntityByPath(
data.code[0].file
)
if (doc == null) {
return
}
return deferred.resolve({ doc, line: data.code[0].line })
} else if (data.code[0].file === '') {
ide.$scope.sync_tex_error = true
setTimeout(() => (ide.$scope.sync_tex_error = false), 4000)
.catch(function(response) {
const error = response.data
return deferred.reject(error)
})
return deferred.promise
},
syncToCode(position, options) {
let v
if (options == null) {
options = {}
}
const deferred = $q.defer()
if (position == null) {
deferred.reject()
return deferred.promise
}
// FIXME: this actually works better if it's halfway across the
// page (or the visible part of the page). Synctex doesn't
// always find the right place in the file when the point is at
// the edge of the page, it sometimes returns the start of the
// next paragraph instead.
const h = position.offset.left
// Compute the vertical position to pass to synctex, which
// works with coordinates increasing from the top of the page
// down. This matches the browser's DOM coordinate of the
// click point, but the pdf position is measured from the
// bottom of the page so we need to invert it.
if (
options.fromPdfPosition &&
(position.pageSize != null ? position.pageSize.height : undefined) !=
null
) {
v = position.pageSize.height - position.offset.top || 0 // measure from pdf point (inverted)
} else {
v = position.offset.top || 0 // measure from html click position
}
// It's not clear exactly where we should sync to if it wasn't directly
// clicked on, but a little bit down from the very top seems best.
if (options.includeVisualOffset) {
v += 72 // use the same value as in pdfViewer highlighting visual offset
}
$http({
url: `/project/${ide.project_id}/sync/pdf`,
method: 'GET',
params: {
page: position.page + 1,
h: h.toFixed(2),
v: v.toFixed(2),
clsiserverid: ide.clsiServerId
}
})
.then(function(response) {
const { data } = response
if (
data.code != null &&
data.code.length > 0 &&
data.code[0].file !== ''
) {
const doc = ide.fileTreeManager.findEntityByPath(
data.code[0].file
)
if (doc == null) {
return
}
})
.catch(function(response) {
const error = response.data
return deferred.reject(error)
})
return deferred.promise
}
}
return synctex
}
])
App.controller('PdfSynctexController', [
'$scope',
'synctex',
'ide',
function($scope, synctex, ide) {
this.cursorPosition = null
ide.$scope.$on('cursor:editor:update', (event, cursorPosition) => {
this.cursorPosition = cursorPosition
})
$scope.syncToPdf = () => {
if (this.cursorPosition == null) {
return
}
return synctex
.syncToPdf(this.cursorPosition)
.then(highlights => ($scope.pdf.highlights = highlights))
}
ide.$scope.$on('cursor:editor:syncToPdf', $scope.syncToPdf)
return ($scope.syncToCode = () =>
synctex
.syncToCode($scope.pdf.position, {
includeVisualOffset: true,
fromPdfPosition: true
return deferred.resolve({ doc, line: data.code[0].line })
} else if (data.code[0].file === '') {
ide.$scope.sync_tex_error = true
setTimeout(() => (ide.$scope.sync_tex_error = false), 4000)
}
})
.catch(function(response) {
const error = response.data
return deferred.reject(error)
})
.then(function(data) {
const { doc, line } = data
return ide.editorManager.openDoc(doc, { gotoLine: line })
}))
}
])
App.controller('PdfLogEntryController', [
'$scope',
'ide',
'event_tracking',
return deferred.promise
}
}
return synctex
})
App.controller('PdfSynctexController', function($scope, synctex, ide) {
this.cursorPosition = null
ide.$scope.$on('cursor:editor:update', (event, cursorPosition) => {
this.cursorPosition = cursorPosition
})
$scope.syncToPdf = () => {
if (this.cursorPosition == null) {
return
}
return synctex
.syncToPdf(this.cursorPosition)
.then(highlights => ($scope.pdf.highlights = highlights))
}
ide.$scope.$on('cursor:editor:syncToPdf', $scope.syncToPdf)
return ($scope.syncToCode = () =>
synctex
.syncToCode($scope.pdf.position, {
includeVisualOffset: true,
fromPdfPosition: true
})
.then(function(data) {
const { doc, line } = data
return ide.editorManager.openDoc(doc, { gotoLine: line })
}))
})
App.controller(
'PdfLogEntryController',
($scope, ide, event_tracking) =>
($scope.openInEditor = function(entry) {
let column, line
@ -1099,25 +1086,24 @@ define([
gotoColumn: column
})
})
])
)
return App.controller('ClearCacheModalController', [
'$scope',
'$modalInstance',
function($scope, $modalInstance) {
$scope.state = { inflight: false }
return App.controller('ClearCacheModalController', function(
$scope,
$modalInstance
) {
$scope.state = { inflight: false }
$scope.clear = function() {
$scope.state.inflight = true
return $scope.clearCache().then(function() {
$scope.state.inflight = false
return $modalInstance.close()
})
}
return ($scope.cancel = () => $modalInstance.dismiss('cancel'))
$scope.clear = function() {
$scope.state.inflight = true
return $scope.clearCache().then(function() {
$scope.state.inflight = false
return $modalInstance.close()
})
}
])
return ($scope.cancel = () => $modalInstance.dismiss('cancel'))
})
})
function __guard__(value, transform) {

View file

@ -16,41 +16,39 @@
define(['base', 'pdfjs-dist/build/pdf'], (App, PDFJS) =>
// app = angular.module 'pdfHighlights', []
App.factory('pdfHighlights', [
function() {
let pdfHighlights
return (pdfHighlights = class pdfHighlights {
constructor(options) {
this.highlightsLayerDiv = options.highlights[0]
this.highlightElements = []
}
App.factory('pdfHighlights', function() {
let pdfHighlights
return (pdfHighlights = class pdfHighlights {
constructor(options) {
this.highlightsLayerDiv = options.highlights[0]
this.highlightElements = []
}
addHighlight(viewport, left, top, width, height) {
let rect = viewport.convertToViewportRectangle([
left,
top,
left + width,
top + height
])
rect = PDFJS.Util.normalizeRect(rect)
const element = document.createElement('div')
element.style.left = Math.floor(rect[0]) + 'px'
element.style.top = Math.floor(rect[1]) + 'px'
element.style.width = Math.ceil(rect[2] - rect[0]) + 'px'
element.style.height = Math.ceil(rect[3] - rect[1]) + 'px'
this.highlightElements.push(element)
this.highlightsLayerDiv.appendChild(element)
return element
}
addHighlight(viewport, left, top, width, height) {
let rect = viewport.convertToViewportRectangle([
left,
top,
left + width,
top + height
])
rect = PDFJS.Util.normalizeRect(rect)
const element = document.createElement('div')
element.style.left = Math.floor(rect[0]) + 'px'
element.style.top = Math.floor(rect[1]) + 'px'
element.style.width = Math.ceil(rect[2] - rect[0]) + 'px'
element.style.height = Math.ceil(rect[3] - rect[1]) + 'px'
this.highlightElements.push(element)
this.highlightsLayerDiv.appendChild(element)
return element
}
clearHighlights() {
for (let h of Array.from(this.highlightElements)) {
if (h != null) {
h.parentNode.removeChild(h)
}
clearHighlights() {
for (let h of Array.from(this.highlightElements)) {
if (h != null) {
h.parentNode.removeChild(h)
}
return (this.highlightElements = [])
}
})
}
]))
return (this.highlightElements = [])
}
})
}))

View file

@ -14,148 +14,143 @@
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
*/
define(['base', 'ide/pdfng/directives/pdfViewer'], (App, pdfViewer) =>
App.directive('pdfng', [
'$timeout',
'localStorage',
($timeout, localStorage) => ({
scope: {
pdfSrc: '=',
highlights: '=',
position: '=',
dblClickCallback: '='
},
link(scope, element, attrs) {
scope.loading = false
scope.pleaseJumpTo = null
scope.scale = null
let initializedPosition = false
const initializePosition = function() {
let position, scale
if (initializedPosition) {
App.directive('pdfng', ($timeout, localStorage) => ({
scope: {
pdfSrc: '=',
highlights: '=',
position: '=',
dblClickCallback: '='
},
link(scope, element, attrs) {
scope.loading = false
scope.pleaseJumpTo = null
scope.scale = null
let initializedPosition = false
const initializePosition = function() {
let position, scale
if (initializedPosition) {
return
}
initializedPosition = true
if ((scale = localStorage('pdf.scale')) != null) {
scope.scale = { scaleMode: scale.scaleMode, scale: +scale.scale }
} else {
scope.scale = { scaleMode: 'scale_mode_fit_width' }
}
if ((position = localStorage(`pdf.position.${attrs.key}`))) {
scope.position = {
page: +position.page,
offset: {
top: +position.offset.top,
left: +position.offset.left
}
}
}
scope.$on('$destroy', () => {
localStorage('pdf.scale', scope.scale)
return localStorage(`pdf.position.${attrs.key}`, scope.position)
})
return $(window).unload(() => {
localStorage('pdf.scale', scope.scale)
return localStorage(`pdf.position.${attrs.key}`, scope.position)
})
}
const flashControls = () =>
scope.$evalAsync(function() {
scope.flashControls = true
return $timeout(() => (scope.flashControls = false), 1000)
})
scope.$on(
'pdfDoubleClick',
(event, e) =>
typeof scope.dblClickCallback === 'function'
? scope.dblClickCallback({
page: e.page - 1,
offset: { top: e.y, left: e.x }
})
: undefined
)
scope.$on('flash-controls', () => flashControls())
scope.$watch('pdfSrc', function(url) {
if (url) {
scope.loading = true
scope.loaded = false
scope.progress = 1
initializePosition()
return flashControls()
}
})
scope.$on('loaded', function() {
scope.loaded = true
scope.progress = 100
return $timeout(function() {
scope.loading = false
return delete scope.progress
}, 500)
})
scope.fitToHeight = function() {
const scale = angular.copy(scope.scale)
scale.scaleMode = 'scale_mode_fit_height'
return (scope.scale = scale)
}
scope.fitToWidth = function() {
const scale = angular.copy(scope.scale)
scale.scaleMode = 'scale_mode_fit_width'
return (scope.scale = scale)
}
scope.zoomIn = function() {
const scale = angular.copy(scope.scale)
scale.scaleMode = 'scale_mode_value'
scale.scale = scale.scale * 1.2
return (scope.scale = scale)
}
scope.zoomOut = function() {
const scale = angular.copy(scope.scale)
scale.scaleMode = 'scale_mode_value'
scale.scale = scale.scale / 1.2
return (scope.scale = scale)
}
if (attrs.resizeOn != null) {
for (let event of Array.from(attrs.resizeOn.split(','))) {
scope.$on(event, function(e) {})
}
}
// console.log 'got a resize event', event, e
scope.$on('progress', (event, progress) =>
scope.$apply(function() {
if (scope.loaded) {
return
}
initializedPosition = true
if ((scale = localStorage('pdf.scale')) != null) {
scope.scale = { scaleMode: scale.scaleMode, scale: +scale.scale }
} else {
scope.scale = { scaleMode: 'scale_mode_fit_width' }
scope.progress = Math.floor((progress.loaded / progress.total) * 100)
if (scope.progress > 100) {
scope.progress = 100
}
if ((position = localStorage(`pdf.position.${attrs.key}`))) {
scope.position = {
page: +position.page,
offset: {
top: +position.offset.top,
left: +position.offset.left
}
}
}
scope.$on('$destroy', () => {
localStorage('pdf.scale', scope.scale)
return localStorage(`pdf.position.${attrs.key}`, scope.position)
})
return $(window).unload(() => {
localStorage('pdf.scale', scope.scale)
return localStorage(`pdf.position.${attrs.key}`, scope.position)
})
}
const flashControls = () =>
scope.$evalAsync(function() {
scope.flashControls = true
return $timeout(() => (scope.flashControls = false), 1000)
})
scope.$on(
'pdfDoubleClick',
(event, e) =>
typeof scope.dblClickCallback === 'function'
? scope.dblClickCallback({
page: e.page - 1,
offset: { top: e.y, left: e.x }
})
: undefined
)
scope.$on('flash-controls', () => flashControls())
scope.$watch('pdfSrc', function(url) {
if (url) {
scope.loading = true
scope.loaded = false
scope.progress = 1
initializePosition()
return flashControls()
if (scope.progress < 0) {
return (scope.progress = 0)
}
})
)
scope.$on('loaded', function() {
scope.loaded = true
scope.progress = 100
return $timeout(function() {
scope.loading = false
return delete scope.progress
}, 500)
})
return scope.$on('$destroy', function() {})
},
// console.log 'pdfjs destroy event'
scope.fitToHeight = function() {
const scale = angular.copy(scope.scale)
scale.scaleMode = 'scale_mode_fit_height'
return (scope.scale = scale)
}
scope.fitToWidth = function() {
const scale = angular.copy(scope.scale)
scale.scaleMode = 'scale_mode_fit_width'
return (scope.scale = scale)
}
scope.zoomIn = function() {
const scale = angular.copy(scope.scale)
scale.scaleMode = 'scale_mode_value'
scale.scale = scale.scale * 1.2
return (scope.scale = scale)
}
scope.zoomOut = function() {
const scale = angular.copy(scope.scale)
scale.scaleMode = 'scale_mode_value'
scale.scale = scale.scale / 1.2
return (scope.scale = scale)
}
if (attrs.resizeOn != null) {
for (let event of Array.from(attrs.resizeOn.split(','))) {
scope.$on(event, function(e) {})
}
}
// console.log 'got a resize event', event, e
scope.$on('progress', (event, progress) =>
scope.$apply(function() {
if (scope.loaded) {
return
}
scope.progress = Math.floor(
(progress.loaded / progress.total) * 100
)
if (scope.progress > 100) {
scope.progress = 100
}
if (scope.progress < 0) {
return (scope.progress = 0)
}
})
)
return scope.$on('$destroy', function() {})
},
// console.log 'pdfjs destroy event'
template: `\
template: `\
<div data-pdf-viewer class="pdfjs-viewer" pdf-src='pdfSrc' position='position' scale='scale' highlights='highlights' please-jump-to='pleaseJumpTo'></div>
<div class="pdfjs-controls" ng-class="{'flash': flashControls }">
<div class="btn-group">
@ -201,5 +196,4 @@ define(['base', 'ide/pdfng/directives/pdfViewer'], (App, pdfViewer) =>
<div class="progress-bar" ng-style="{ 'width': progress + '%' }"></div>
</div>\
`
})
]))
})))

View file

@ -17,13 +17,9 @@
define(['base'], App =>
// App = angular.module 'pdfPage', ['pdfHighlights']
App.directive('pdfPage', [
'$timeout',
'pdfHighlights',
'pdfSpinner',
($timeout, pdfHighlights, pdfSpinner) => ({
require: '^pdfViewer',
template: `\
App.directive('pdfPage', ($timeout, pdfHighlights, pdfSpinner) => ({
require: '^pdfViewer',
template: `\
<div class="plv-page-view page-view">
<div class="pdf-canvas pdfng-empty"></div>
<div class="plv-text-layer text-layer"></div>
@ -31,141 +27,140 @@ define(['base'], App =>
<div class="plv-highlights-layer highlights-layer"></div>
</div>\
`,
link(scope, element, attrs, ctrl) {
const canvasElement = $(element).find('.pdf-canvas')
const textElement = $(element).find('.text-layer')
const annotationsElement = $(element).find('.annotations-layer')
const highlightsElement = $(element).find('.highlights-layer')
link(scope, element, attrs, ctrl) {
const canvasElement = $(element).find('.pdf-canvas')
const textElement = $(element).find('.text-layer')
const annotationsElement = $(element).find('.annotations-layer')
const highlightsElement = $(element).find('.highlights-layer')
const updatePageSize = function(size) {
const h = Math.floor(size[0])
const w = Math.floor(size[1])
element.height(h)
element.width(w)
canvasElement.height(h)
canvasElement.width(w)
return (scope.page.sized = true)
}
// keep track of our page element, so we can access it in the
// parent with scope.pages[i].element, and the contained
// elements for each part
scope.page.element = element
scope.page.elementChildren = {
canvas: canvasElement,
text: textElement,
annotations: annotationsElement,
highlights: highlightsElement,
container: element
}
if (!scope.page.sized) {
if (scope.defaultPageSize != null) {
updatePageSize(scope.defaultPageSize)
} else {
// shouldn't get here - the default page size should now
// always be set before redraw is called
var handler = scope.$watch('defaultPageSize', function(
defaultPageSize
) {
if (defaultPageSize == null) {
return
}
updatePageSize(defaultPageSize)
return handler()
})
}
}
if (scope.page.current) {
// console.log 'we must scroll to this page', scope.page.pageNum, 'at position', scope.page.position
// this is the current page, we want to scroll it into view
// and render it immediately
scope.document.renderPage(scope.page)
ctrl.setPdfPosition(scope.page, scope.page.position)
}
element.on('dblclick', function(e) {
const offset = $(element)
.find('.pdf-canvas')
.offset()
const dx = e.pageX - offset.left
const dy = e.pageY - offset.top
return scope.document
.getPdfViewport(scope.page.pageNum)
.then(function(viewport) {
const pdfPoint = viewport.convertToPdfPoint(dx, dy)
const event = {
page: scope.page.pageNum,
x: pdfPoint[0],
y: viewport.viewBox[3] - pdfPoint[1]
}
return scope.$emit('pdfDoubleClick', event)
})
})
const highlightsLayer = new pdfHighlights({
highlights: highlightsElement
})
scope.$on('pdf:highlights', function(event, highlights) {
let h
if (highlights == null) {
return
}
if (!(highlights.length > 0)) {
return
}
if (scope.timeoutHandler) {
$timeout.cancel(scope.timeoutHandler)
highlightsLayer.clearHighlights()
scope.timeoutHandler = null
}
// console.log 'got highlight watch in pdfPage', scope.page
const pageHighlights = (() => {
const result = []
for (h of Array.from(highlights)) {
if (h.page === scope.page.pageNum) {
result.push(h)
}
}
return result
})()
if (!pageHighlights.length) {
return
}
scope.document.getPdfViewport(scope.page.pageNum).then(viewport =>
(() => {
const result1 = []
for (let hl of Array.from(pageHighlights)) {
// console.log 'adding highlight', h, viewport
const top = viewport.viewBox[3] - hl.v
result1.push(
highlightsLayer.addHighlight(
viewport,
hl.h,
top,
hl.width,
hl.height
)
)
}
return result1
})()
)
return (scope.timeoutHandler = $timeout(function() {
highlightsLayer.clearHighlights()
return (scope.timeoutHandler = null)
}, 1000))
})
return scope.$on('$destroy', function() {
if (scope.timeoutHandler != null) {
$timeout.cancel(scope.timeoutHandler)
return highlightsLayer.clearHighlights()
}
})
const updatePageSize = function(size) {
const h = Math.floor(size[0])
const w = Math.floor(size[1])
element.height(h)
element.width(w)
canvasElement.height(h)
canvasElement.width(w)
return (scope.page.sized = true)
}
})
]))
// keep track of our page element, so we can access it in the
// parent with scope.pages[i].element, and the contained
// elements for each part
scope.page.element = element
scope.page.elementChildren = {
canvas: canvasElement,
text: textElement,
annotations: annotationsElement,
highlights: highlightsElement,
container: element
}
if (!scope.page.sized) {
if (scope.defaultPageSize != null) {
updatePageSize(scope.defaultPageSize)
} else {
// shouldn't get here - the default page size should now
// always be set before redraw is called
var handler = scope.$watch('defaultPageSize', function(
defaultPageSize
) {
if (defaultPageSize == null) {
return
}
updatePageSize(defaultPageSize)
return handler()
})
}
}
if (scope.page.current) {
// console.log 'we must scroll to this page', scope.page.pageNum, 'at position', scope.page.position
// this is the current page, we want to scroll it into view
// and render it immediately
scope.document.renderPage(scope.page)
ctrl.setPdfPosition(scope.page, scope.page.position)
}
element.on('dblclick', function(e) {
const offset = $(element)
.find('.pdf-canvas')
.offset()
const dx = e.pageX - offset.left
const dy = e.pageY - offset.top
return scope.document
.getPdfViewport(scope.page.pageNum)
.then(function(viewport) {
const pdfPoint = viewport.convertToPdfPoint(dx, dy)
const event = {
page: scope.page.pageNum,
x: pdfPoint[0],
y: viewport.viewBox[3] - pdfPoint[1]
}
return scope.$emit('pdfDoubleClick', event)
})
})
const highlightsLayer = new pdfHighlights({
highlights: highlightsElement
})
scope.$on('pdf:highlights', function(event, highlights) {
let h
if (highlights == null) {
return
}
if (!(highlights.length > 0)) {
return
}
if (scope.timeoutHandler) {
$timeout.cancel(scope.timeoutHandler)
highlightsLayer.clearHighlights()
scope.timeoutHandler = null
}
// console.log 'got highlight watch in pdfPage', scope.page
const pageHighlights = (() => {
const result = []
for (h of Array.from(highlights)) {
if (h.page === scope.page.pageNum) {
result.push(h)
}
}
return result
})()
if (!pageHighlights.length) {
return
}
scope.document.getPdfViewport(scope.page.pageNum).then(viewport =>
(() => {
const result1 = []
for (let hl of Array.from(pageHighlights)) {
// console.log 'adding highlight', h, viewport
const top = viewport.viewBox[3] - hl.v
result1.push(
highlightsLayer.addHighlight(
viewport,
hl.h,
top,
hl.width,
hl.height
)
)
}
return result1
})()
)
return (scope.timeoutHandler = $timeout(function() {
highlightsLayer.clearHighlights()
return (scope.timeoutHandler = null)
}, 1000))
})
return scope.$on('$destroy', function() {
if (scope.timeoutHandler != null) {
$timeout.cancel(scope.timeoutHandler)
return highlightsLayer.clearHighlights()
}
})
}
})))

View file

@ -21,512 +21,503 @@
define(['base', 'pdfjs-dist/build/pdf'], (App, PDFJS) =>
// App = angular.module 'PDFRenderer', ['pdfAnnotations', 'pdfTextLayer']
App.factory('PDFRenderer', [
'$q',
'$timeout',
'pdfAnnotations',
'pdfTextLayer',
'pdfSpinner',
function($q, $timeout, pdfAnnotations, pdfTextLayer, pdfSpinner) {
let PDFRenderer
return (PDFRenderer = (function() {
PDFRenderer = class PDFRenderer {
static initClass() {
this.prototype.JOB_QUEUE_INTERVAL = 25
this.prototype.PAGE_LOAD_TIMEOUT = 60 * 1000
this.prototype.INDICATOR_DELAY1 = 100 // time to delay before showing the indicator
this.prototype.INDICATOR_DELAY2 = 250 // time until the indicator starts animating
this.prototype.TEXTLAYER_TIMEOUT = 100
App.factory('PDFRenderer', function(
$q,
$timeout,
pdfAnnotations,
pdfTextLayer,
pdfSpinner
) {
let PDFRenderer
return (PDFRenderer = (function() {
PDFRenderer = class PDFRenderer {
static initClass() {
this.prototype.JOB_QUEUE_INTERVAL = 25
this.prototype.PAGE_LOAD_TIMEOUT = 60 * 1000
this.prototype.INDICATOR_DELAY1 = 100 // time to delay before showing the indicator
this.prototype.INDICATOR_DELAY2 = 250 // time until the indicator starts animating
this.prototype.TEXTLAYER_TIMEOUT = 100
}
constructor(url, options) {
// set up external character mappings - needed for Japanese etc
this.url = url
this.options = options
this.scale = this.options.scale || 1
let disableFontFace
if (
__guard__(
window.location != null ? window.location.search : undefined,
x => x.indexOf('disable-font-face=true')
) >= 0
) {
disableFontFace = true
} else {
disableFontFace = false
}
constructor(url, options) {
// set up external character mappings - needed for Japanese etc
this.url = url
this.options = options
this.scale = this.options.scale || 1
let disableFontFace
if (
__guard__(
window.location != null ? window.location.search : undefined,
x => x.indexOf('disable-font-face=true')
) >= 0
) {
disableFontFace = true
} else {
disableFontFace = false
}
this.pdfjs = PDFJS.getDocument({
url: this.url,
cMapUrl: window.pdfCMapsPath,
cMapPacked: true,
disableFontFace,
// Enable fetching with Range headers to restrict individual
// requests to 128kb.
// To do this correctly we must:
// a) disable auto-fetching of the whole file upfront
// b) disable streaming (which in this context means streaming of
// the response into memory). This isn't supported when using
// Range headers, but shouldn't be a problem since we are already
// limiting individual response size through chunked range
// requests
rangeChunkSize: 128 * 1024,
disableAutoFetch: !!this.options.disableAutoFetch,
disableStream: !!this.options.disableAutoFetch
this.pdfjs = PDFJS.getDocument({
url: this.url,
cMapUrl: window.pdfCMapsPath,
cMapPacked: true,
disableFontFace,
// Enable fetching with Range headers to restrict individual
// requests to 128kb.
// To do this correctly we must:
// a) disable auto-fetching of the whole file upfront
// b) disable streaming (which in this context means streaming of
// the response into memory). This isn't supported when using
// Range headers, but shouldn't be a problem since we are already
// limiting individual response size through chunked range
// requests
rangeChunkSize: 128 * 1024,
disableAutoFetch: !!this.options.disableAutoFetch,
disableStream: !!this.options.disableAutoFetch
})
this.pdfjs.onProgress = this.options.progressCallback
this.document = $q.when(this.pdfjs)
this.navigateFn = this.options.navigateFn
this.spinner = new pdfSpinner()
this.resetState()
this.document.then(pdfDocument => {
return pdfDocument.getDownloadInfo().then(() => {
return this.options.loadedCallback()
})
this.pdfjs.onProgress = this.options.progressCallback
this.document = $q.when(this.pdfjs)
this.navigateFn = this.options.navigateFn
this.spinner = new pdfSpinner()
this.resetState()
this.document.then(pdfDocument => {
return pdfDocument.getDownloadInfo().then(() => {
return this.options.loadedCallback()
})
})
this.errorCallback = this.options.errorCallback
this.pageSizeChangeCallback = this.options.pageSizeChangeCallback
this.pdfjs.promise.catch(exception => {
// error getting document
return this.errorCallback(exception)
})
}
})
this.errorCallback = this.options.errorCallback
this.pageSizeChangeCallback = this.options.pageSizeChangeCallback
this.pdfjs.promise.catch(exception => {
// error getting document
return this.errorCallback(exception)
})
}
resetState() {
this.renderQueue = []
if (this.queueTimer != null) {
clearTimeout(this.queueTimer)
}
// clear any existing timers, render tasks
for (let timer of Array.from(this.spinTimer || [])) {
clearTimeout(timer)
}
for (let page of Array.from(this.pageState || [])) {
__guard__(page != null ? page.loadTask : undefined, x =>
x.cancel()
)
__guard__(page != null ? page.renderTask : undefined, x1 =>
x1.cancel()
)
}
// initialise/reset the state
this.pageState = []
this.spinTimer = [] // timers for starting the spinners (to avoid jitter)
this.spinTimerDone = [] // array of pages where the spinner has activated
return (this.jobs = 0)
resetState() {
this.renderQueue = []
if (this.queueTimer != null) {
clearTimeout(this.queueTimer)
}
getNumPages() {
return this.document.then(pdfDocument => pdfDocument.numPages)
// clear any existing timers, render tasks
for (let timer of Array.from(this.spinTimer || [])) {
clearTimeout(timer)
}
getPage(pageNum) {
return this.document.then(pdfDocument =>
pdfDocument.getPage(pageNum)
for (let page of Array.from(this.pageState || [])) {
__guard__(page != null ? page.loadTask : undefined, x => x.cancel())
__guard__(page != null ? page.renderTask : undefined, x1 =>
x1.cancel()
)
}
// initialise/reset the state
this.pageState = []
this.spinTimer = [] // timers for starting the spinners (to avoid jitter)
this.spinTimerDone = [] // array of pages where the spinner has activated
return (this.jobs = 0)
}
getPdfViewport(pageNum, scale) {
if (scale == null) {
;({ scale } = this)
}
return this.document.then(pdfDocument => {
return pdfDocument.getPage(pageNum).then(
function(page) {
let viewport
return (viewport = page.getViewport(scale))
},
error => {
return typeof this.errorCallback === 'function'
? this.errorCallback(error)
: undefined
}
)
})
getNumPages() {
return this.document.then(pdfDocument => pdfDocument.numPages)
}
getPage(pageNum) {
return this.document.then(pdfDocument => pdfDocument.getPage(pageNum))
}
getPdfViewport(pageNum, scale) {
if (scale == null) {
;({ scale } = this)
}
getDestinations() {
return this.document.then(pdfDocument =>
pdfDocument.getDestinations()
)
}
getDestination(dest) {
return this.document.then(
pdfDocument => pdfDocument.getDestination(dest),
return this.document.then(pdfDocument => {
return pdfDocument.getPage(pageNum).then(
function(page) {
let viewport
return (viewport = page.getViewport(scale))
},
error => {
return typeof this.errorCallback === 'function'
? this.errorCallback(error)
: undefined
}
)
}
})
}
getPageIndex(ref) {
return this.document.then(pdfDocument => {
return pdfDocument.getPageIndex(ref).then(
idx => idx,
error => {
return typeof this.errorCallback === 'function'
? this.errorCallback(error)
: undefined
}
)
})
}
getDestinations() {
return this.document.then(pdfDocument =>
pdfDocument.getDestinations()
)
}
getScale() {
return this.scale
}
setScale(scale) {
this.scale = scale
return this.resetState()
}
triggerRenderQueue(interval) {
if (interval == null) {
interval = this.JOB_QUEUE_INTERVAL
getDestination(dest) {
return this.document.then(
pdfDocument => pdfDocument.getDestination(dest),
error => {
return typeof this.errorCallback === 'function'
? this.errorCallback(error)
: undefined
}
if (this.queueTimer != null) {
clearTimeout(this.queueTimer)
}
return (this.queueTimer = setTimeout(() => {
this.queueTimer = null
return this.processRenderQueue()
}, interval))
}
)
}
removeCompletedJob(pagenum) {
this.jobs = this.jobs - 1
return this.triggerRenderQueue(0)
}
getPageIndex(ref) {
return this.document.then(pdfDocument => {
return pdfDocument.getPageIndex(ref).then(
idx => idx,
error => {
return typeof this.errorCallback === 'function'
? this.errorCallback(error)
: undefined
}
)
})
}
renderPages(pages) {
if (this.shuttingDown) {
return
}
this.renderQueue = Array.from(pages).map(page => ({
element: page.elementChildren,
pagenum: page.pageNum
}))
return this.triggerRenderQueue()
}
getScale() {
return this.scale
}
renderPage(page) {
if (this.shuttingDown) {
return
}
const current = {
element: page.elementChildren,
pagenum: page.pageNum
}
this.renderQueue.push(current)
setScale(scale) {
this.scale = scale
return this.resetState()
}
triggerRenderQueue(interval) {
if (interval == null) {
interval = this.JOB_QUEUE_INTERVAL
}
if (this.queueTimer != null) {
clearTimeout(this.queueTimer)
}
return (this.queueTimer = setTimeout(() => {
this.queueTimer = null
return this.processRenderQueue()
}, interval))
}
removeCompletedJob(pagenum) {
this.jobs = this.jobs - 1
return this.triggerRenderQueue(0)
}
renderPages(pages) {
if (this.shuttingDown) {
return
}
this.renderQueue = Array.from(pages).map(page => ({
element: page.elementChildren,
pagenum: page.pageNum
}))
return this.triggerRenderQueue()
}
getPageDetails(page) {
return [page.element.canvas, page.pagenum]
renderPage(page) {
if (this.shuttingDown) {
return
}
const current = {
element: page.elementChildren,
pagenum: page.pageNum
}
this.renderQueue.push(current)
return this.processRenderQueue()
}
// handle the loading indicators for each page
getPageDetails(page) {
return [page.element.canvas, page.pagenum]
}
startIndicators() {
// make an array of the pages in the queue
this.queuedPages = []
for (var page of Array.from(this.renderQueue)) {
this.queuedPages[page.pagenum] = true
// handle the loading indicators for each page
startIndicators() {
// make an array of the pages in the queue
this.queuedPages = []
for (var page of Array.from(this.renderQueue)) {
this.queuedPages[page.pagenum] = true
}
// clear any unfinished spinner timers on pages that aren't in the queue any more
for (let pagenum in this.spinTimer) {
if (!this.queuedPages[pagenum]) {
clearTimeout(this.spinTimer[pagenum])
delete this.spinTimer[pagenum]
}
// clear any unfinished spinner timers on pages that aren't in the queue any more
for (let pagenum in this.spinTimer) {
if (!this.queuedPages[pagenum]) {
clearTimeout(this.spinTimer[pagenum])
delete this.spinTimer[pagenum]
}
// add indicators for any new pages in the current queue
return (() => {
const result = []
for (page of Array.from(this.renderQueue)) {
if (
!this.spinTimer[page.pagenum] &&
!this.spinTimerDone[page.pagenum]
) {
result.push(this.startIndicator(page))
}
}
// add indicators for any new pages in the current queue
return (() => {
const result = []
for (page of Array.from(this.renderQueue)) {
if (
!this.spinTimer[page.pagenum] &&
!this.spinTimerDone[page.pagenum]
) {
result.push(this.startIndicator(page))
}
}
return result
})()
}
return result
})()
}
startIndicator(page) {
const [canvas, pagenum] = Array.from(this.getPageDetails(page))
canvas.addClass('pdfng-loading')
startIndicator(page) {
const [canvas, pagenum] = Array.from(this.getPageDetails(page))
canvas.addClass('pdfng-loading')
return (this.spinTimer[pagenum] = setTimeout(() => {
for (let queuedPage of Array.from(this.renderQueue)) {
if (pagenum === queuedPage.pagenum) {
this.spinner.add(canvas, { static: true })
this.spinTimerDone[pagenum] = true
break
}
}
return delete this.spinTimer[pagenum]
}, this.INDICATOR_DELAY1))
}
updateIndicator(page) {
const [canvas, pagenum] = Array.from(this.getPageDetails(page))
// did the spinner insert itself already?
if (this.spinTimerDone[pagenum]) {
return (this.spinTimer[pagenum] = setTimeout(() => {
for (let queuedPage of Array.from(this.renderQueue)) {
if (pagenum === queuedPage.pagenum) {
this.spinner.add(canvas, { static: true })
this.spinTimerDone[pagenum] = true
break
}
}
this.spinner.start(canvas)
return delete this.spinTimer[pagenum]
}, this.INDICATOR_DELAY1))
}
updateIndicator(page) {
const [canvas, pagenum] = Array.from(this.getPageDetails(page))
// did the spinner insert itself already?
if (this.spinTimerDone[pagenum]) {
}, this.INDICATOR_DELAY2))
} else {
// stop the existing spin timer
clearTimeout(this.spinTimer[pagenum])
// start a new one which will also start spinning
return (this.spinTimer[pagenum] = setTimeout(() => {
this.spinner.add(canvas, { static: true })
this.spinTimerDone[pagenum] = true
return (this.spinTimer[pagenum] = setTimeout(() => {
this.spinner.start(canvas)
return delete this.spinTimer[pagenum]
}, this.INDICATOR_DELAY2))
} else {
// stop the existing spin timer
clearTimeout(this.spinTimer[pagenum])
// start a new one which will also start spinning
return (this.spinTimer[pagenum] = setTimeout(() => {
this.spinner.add(canvas, { static: true })
this.spinTimerDone[pagenum] = true
return (this.spinTimer[pagenum] = setTimeout(() => {
this.spinner.start(canvas)
return delete this.spinTimer[pagenum]
}, this.INDICATOR_DELAY2))
}, this.INDICATOR_DELAY1))
}
}, this.INDICATOR_DELAY1))
}
}
clearIndicator(page) {
const [canvas, pagenum] = Array.from(this.getPageDetails(page))
this.spinner.stop(canvas)
clearTimeout(this.spinTimer[pagenum])
delete this.spinTimer[pagenum]
return (this.spinTimerDone[pagenum] = true)
}
// handle the queue of pages to be rendered
processRenderQueue() {
let pageState
if (this.shuttingDown) {
return
}
// mark all pages in the queue as loading
this.startIndicators()
// bail out if there is already a render job running
if (this.jobs > 0) {
return
}
// take the first page in the queue
let page = this.renderQueue.shift()
// check if it is in action already
while (page != null && this.pageState[page.pagenum] != null) {
page = this.renderQueue.shift()
}
if (page == null) {
return
}
const [element, pagenum] = Array.from([page.element, page.pagenum])
this.jobs = this.jobs + 1
// update the spinner to make it spinning (signifies loading has begun)
this.updateIndicator(page)
let timedOut = false
const timer = $timeout(() => {
// page load timed out
if (loadTask.cancelled) {
return
} // return from cancelled page load
__guardMethod__(window.Raven, 'captureMessage', o =>
o.captureMessage(
`pdfng page load timed out after ${this.PAGE_LOAD_TIMEOUT}ms`
)
)
timedOut = true
this.clearIndicator(page)
// @jobs = @jobs - 1
// @triggerRenderQueue(0)
return typeof this.errorCallback === 'function'
? this.errorCallback('timeout')
: undefined
}, this.PAGE_LOAD_TIMEOUT)
var loadTask = this.getPage(pagenum)
loadTask.cancel = function() {
return (this.cancelled = true)
}
clearIndicator(page) {
const [canvas, pagenum] = Array.from(this.getPageDetails(page))
this.spinner.stop(canvas)
clearTimeout(this.spinTimer[pagenum])
delete this.spinTimer[pagenum]
return (this.spinTimerDone[pagenum] = true)
}
this.pageState[pagenum] = pageState = { loadTask }
// handle the queue of pages to be rendered
processRenderQueue() {
let pageState
if (this.shuttingDown) {
return
}
// mark all pages in the queue as loading
this.startIndicators()
// bail out if there is already a render job running
if (this.jobs > 0) {
return
}
// take the first page in the queue
let page = this.renderQueue.shift()
// check if it is in action already
while (page != null && this.pageState[page.pagenum] != null) {
page = this.renderQueue.shift()
}
if (page == null) {
return
}
const [element, pagenum] = Array.from([page.element, page.pagenum])
this.jobs = this.jobs + 1
// update the spinner to make it spinning (signifies loading has begun)
this.updateIndicator(page)
let timedOut = false
const timer = $timeout(() => {
// page load timed out
return loadTask
.then(pageObject => {
// page load success
$timeout.cancel(timer)
if (loadTask.cancelled) {
return
} // return from cancelled page load
__guardMethod__(window.Raven, 'captureMessage', o =>
o.captureMessage(
`pdfng page load timed out after ${this.PAGE_LOAD_TIMEOUT}ms`
)
pageState.renderTask = this.doRender(element, pagenum, pageObject)
return pageState.renderTask.then(
() => {
// render task success
this.clearIndicator(page)
pageState.complete = true
delete pageState.renderTask
return this.removeCompletedJob(pagenum)
},
() => {
// render task failed
// could display an error icon
pageState.complete = false
delete pageState.renderTask
return this.removeCompletedJob(pagenum)
}
)
timedOut = true
this.clearIndicator(page)
// @jobs = @jobs - 1
// @triggerRenderQueue(0)
return typeof this.errorCallback === 'function'
? this.errorCallback('timeout')
: undefined
}, this.PAGE_LOAD_TIMEOUT)
})
.catch(error => {
// page load error
$timeout.cancel(timer)
return this.clearIndicator(page)
})
}
var loadTask = this.getPage(pagenum)
doRender(element, pagenum, page) {
const self = this
const { scale } = this
loadTask.cancel = function() {
return (this.cancelled = true)
}
this.pageState[pagenum] = pageState = { loadTask }
return loadTask
.then(pageObject => {
// page load success
$timeout.cancel(timer)
if (loadTask.cancelled) {
return
} // return from cancelled page load
pageState.renderTask = this.doRender(
element,
pagenum,
pageObject
)
return pageState.renderTask.then(
() => {
// render task success
this.clearIndicator(page)
pageState.complete = true
delete pageState.renderTask
return this.removeCompletedJob(pagenum)
},
() => {
// render task failed
// could display an error icon
pageState.complete = false
delete pageState.renderTask
return this.removeCompletedJob(pagenum)
}
)
})
.catch(error => {
// page load error
$timeout.cancel(timer)
return this.clearIndicator(page)
})
if (scale == null) {
// scale is undefined, returning
return
}
doRender(element, pagenum, page) {
const self = this
const { scale } = this
const canvas = $(
'<canvas class="pdf-canvas pdfng-rendering"></canvas>'
)
// In Windows+IE we must have the canvas in the DOM during
// rendering to see the fonts defined in the DOM. If we try to
// render 'offscreen' then all the text will be sans-serif.
// Previously we rendered offscreen and added in the canvas
// when rendering was complete.
element.canvas.replaceWith(canvas)
if (scale == null) {
// scale is undefined, returning
return
const viewport = page.getViewport(scale)
const devicePixelRatio = window.devicePixelRatio || 1
const ctx = canvas[0].getContext('2d')
const backingStoreRatio =
ctx.webkitBackingStorePixelRatio ||
ctx.mozBackingStorePixelRatio ||
ctx.msBackingStorePixelRatio ||
ctx.oBackingStorePixelRatio ||
ctx.backingStorePixelRatio ||
1
const pixelRatio = devicePixelRatio / backingStoreRatio
const scaledWidth = (Math.floor(viewport.width) * pixelRatio) | 0
const scaledHeight = (Math.floor(viewport.height) * pixelRatio) | 0
const newWidth = Math.floor(viewport.width)
const newHeight = Math.floor(viewport.height)
canvas[0].height = scaledHeight
canvas[0].width = scaledWidth
canvas.height(newHeight + 'px')
canvas.width(newWidth + 'px')
const oldHeight = element.canvas.height()
const oldWidth = element.canvas.width()
if (newHeight !== oldHeight || newWidth !== oldWidth) {
element.canvas.height(newHeight + 'px')
element.canvas.width(newWidth + 'px')
element.container.height(newHeight + 'px')
element.container.width(newWidth + 'px')
if (typeof this.pageSizeChangeCallback === 'function') {
this.pageSizeChangeCallback(pagenum, newHeight - oldHeight)
}
}
const canvas = $(
'<canvas class="pdf-canvas pdfng-rendering"></canvas>'
)
// In Windows+IE we must have the canvas in the DOM during
// rendering to see the fonts defined in the DOM. If we try to
// render 'offscreen' then all the text will be sans-serif.
// Previously we rendered offscreen and added in the canvas
// when rendering was complete.
element.canvas.replaceWith(canvas)
const textLayer = new pdfTextLayer({
textLayerDiv: element.text[0],
viewport,
renderer: PDFJS.renderTextLayer
})
const viewport = page.getViewport(scale)
const annotationsLayer = new pdfAnnotations({
annotations: element.annotations[0],
viewport,
navigateFn: this.navigateFn
})
const devicePixelRatio = window.devicePixelRatio || 1
const result = page.render({
canvasContext: ctx,
viewport,
transform: [pixelRatio, 0, 0, pixelRatio, 0, 0]
})
const ctx = canvas[0].getContext('2d')
const backingStoreRatio =
ctx.webkitBackingStorePixelRatio ||
ctx.mozBackingStorePixelRatio ||
ctx.msBackingStorePixelRatio ||
ctx.oBackingStorePixelRatio ||
ctx.backingStorePixelRatio ||
1
const pixelRatio = devicePixelRatio / backingStoreRatio
const textLayerTimeout = this.TEXTLAYER_TIMEOUT
const scaledWidth = (Math.floor(viewport.width) * pixelRatio) | 0
const scaledHeight = (Math.floor(viewport.height) * pixelRatio) | 0
const newWidth = Math.floor(viewport.width)
const newHeight = Math.floor(viewport.height)
canvas[0].height = scaledHeight
canvas[0].width = scaledWidth
canvas.height(newHeight + 'px')
canvas.width(newWidth + 'px')
const oldHeight = element.canvas.height()
const oldWidth = element.canvas.width()
if (newHeight !== oldHeight || newWidth !== oldWidth) {
element.canvas.height(newHeight + 'px')
element.canvas.width(newWidth + 'px')
element.container.height(newHeight + 'px')
element.container.width(newWidth + 'px')
if (typeof this.pageSizeChangeCallback === 'function') {
this.pageSizeChangeCallback(pagenum, newHeight - oldHeight)
}
}
const textLayer = new pdfTextLayer({
textLayerDiv: element.text[0],
viewport,
renderer: PDFJS.renderTextLayer
})
const annotationsLayer = new pdfAnnotations({
annotations: element.annotations[0],
viewport,
navigateFn: this.navigateFn
})
const result = page.render({
canvasContext: ctx,
viewport,
transform: [pixelRatio, 0, 0, pixelRatio, 0, 0]
})
const textLayerTimeout = this.TEXTLAYER_TIMEOUT
result
.then(function() {
// page render success
canvas.removeClass('pdfng-rendering')
page.getTextContent({ normalizeWhitespace: true }).then(
function(textContent) {
textLayer.setTextContent(textContent)
return textLayer.render(textLayerTimeout)
},
result
.then(function() {
// page render success
canvas.removeClass('pdfng-rendering')
page.getTextContent({ normalizeWhitespace: true }).then(
function(textContent) {
textLayer.setTextContent(textContent)
return textLayer.render(textLayerTimeout)
},
error =>
typeof self.errorCallback === 'function'
? self.errorCallback(error)
: undefined
)
return page
.getAnnotations()
.then(
annotations => annotationsLayer.setAnnotations(annotations),
error =>
typeof self.errorCallback === 'function'
? self.errorCallback(error)
: undefined
)
return page
.getAnnotations()
.then(
annotations => annotationsLayer.setAnnotations(annotations),
error =>
typeof self.errorCallback === 'function'
? self.errorCallback(error)
: undefined
)
})
.catch(function(error) {
// page render failed
if (error.name === 'RenderingCancelledException') {
// do nothing when cancelled
} else {
return typeof self.errorCallback === 'function'
? self.errorCallback(error)
: undefined
}
})
return result
}
destroy() {
this.shuttingDown = true
this.resetState()
return this.pdfjs.then(function(document) {
document.cleanup()
return document.destroy()
})
}
.catch(function(error) {
// page render failed
if (error.name === 'RenderingCancelledException') {
// do nothing when cancelled
} else {
return typeof self.errorCallback === 'function'
? self.errorCallback(error)
: undefined
}
})
return result
}
PDFRenderer.initClass()
return PDFRenderer
})())
}
]))
destroy() {
this.shuttingDown = true
this.resetState()
return this.pdfjs.then(function(document) {
document.cleanup()
return document.destroy()
})
}
}
PDFRenderer.initClass()
return PDFRenderer
})())
}))
function __guard__(value, transform) {
return typeof value !== 'undefined' && value !== null

View file

@ -13,35 +13,33 @@
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
*/
define(['base'], App =>
App.factory('pdfSpinner', [
function() {
let pdfSpinner
return (pdfSpinner = class pdfSpinner {
constructor() {}
// handler for spinners
App.factory('pdfSpinner', function() {
let pdfSpinner
return (pdfSpinner = class pdfSpinner {
constructor() {}
// handler for spinners
add(element, options) {
const size = 64
const spinner = $(
`<div class="pdfng-spinner" style="position: absolute; top: 50%; left:50%; transform: translateX(-50%) translateY(-50%);"><i class="fa fa-spinner${
(options != null ? options.static : undefined) ? '' : ' fa-spin'
}" style="color: #999"></i></div>`
)
spinner.css({ 'font-size': size + 'px' })
return element.append(spinner)
}
add(element, options) {
const size = 64
const spinner = $(
`<div class="pdfng-spinner" style="position: absolute; top: 50%; left:50%; transform: translateX(-50%) translateY(-50%);"><i class="fa fa-spinner${
(options != null ? options.static : undefined) ? '' : ' fa-spin'
}" style="color: #999"></i></div>`
)
spinner.css({ 'font-size': size + 'px' })
return element.append(spinner)
}
start(element) {
return element.find('.fa-spinner').addClass('fa-spin')
}
start(element) {
return element.find('.fa-spinner').addClass('fa-spin')
}
stop(element) {
return element.find('.fa-spinner').removeClass('fa-spin')
}
stop(element) {
return element.find('.fa-spinner').removeClass('fa-spin')
}
remove(element) {
return element.find('.fa-spinner').remove()
}
})
}
]))
remove(element) {
return element.find('.fa-spinner').remove()
}
})
}))

View file

@ -16,65 +16,63 @@ define(['base'], App =>
// uses the PDFJS text layer renderer to provide invisible overlayed
// text for searching
App.factory('pdfTextLayer', [
function() {
let pdfTextLayer
return (pdfTextLayer = class pdfTextLayer {
constructor(options) {
this.textLayerDiv = options.textLayerDiv
this.divContentDone = false
this.viewport = options.viewport
this.textDivs = []
this.renderer = options.renderer
this.renderingDone = false
App.factory('pdfTextLayer', function() {
let pdfTextLayer
return (pdfTextLayer = class pdfTextLayer {
constructor(options) {
this.textLayerDiv = options.textLayerDiv
this.divContentDone = false
this.viewport = options.viewport
this.textDivs = []
this.renderer = options.renderer
this.renderingDone = false
}
render(timeout) {
if (this.renderingDone || !this.divContentDone) {
return
}
render(timeout) {
if (this.renderingDone || !this.divContentDone) {
return
}
if (this.textLayerRenderTask != null) {
this.textLayerRenderTask.cancel()
this.textLayerRenderTask = null
}
this.textDivs = []
const textLayerFrag = document.createDocumentFragment()
this.textLayerRenderTask = this.renderer({
textContent: this.textContent,
container: textLayerFrag,
viewport: this.viewport,
textDivs: this.textDivs,
timeout,
enhanceTextSelection: this.enhanceTextSelection
})
const textLayerSuccess = () => {
this.textLayerDiv.appendChild(textLayerFrag)
return (this.renderingDone = true)
}
const textLayerFailure = function() {
// canceled or failed to render text layer -- skipping errors
}
return this.textLayerRenderTask.promise.then(
textLayerSuccess,
textLayerFailure
)
if (this.textLayerRenderTask != null) {
this.textLayerRenderTask.cancel()
this.textLayerRenderTask = null
}
setTextContent(textContent) {
if (this.textLayerRenderTask) {
this.textLayerRenderTask.cancel()
this.textLayerRenderTask = null
}
this.textDivs = []
const textLayerFrag = document.createDocumentFragment()
this.textContent = textContent
return (this.divContentDone = true)
this.textLayerRenderTask = this.renderer({
textContent: this.textContent,
container: textLayerFrag,
viewport: this.viewport,
textDivs: this.textDivs,
timeout,
enhanceTextSelection: this.enhanceTextSelection
})
const textLayerSuccess = () => {
this.textLayerDiv.appendChild(textLayerFrag)
return (this.renderingDone = true)
}
})
}
]))
const textLayerFailure = function() {
// canceled or failed to render text layer -- skipping errors
}
return this.textLayerRenderTask.promise.then(
textLayerSuccess,
textLayerFailure
)
}
setTextContent(textContent) {
if (this.textLayerRenderTask) {
this.textLayerRenderTask.cancel()
this.textLayerRenderTask = null
}
this.textContent = textContent
return (this.divContentDone = true)
}
})
}))

File diff suppressed because it is too large Load diff

View file

@ -16,113 +16,111 @@
define(['base'], function(App) {
// We create and provide this as service so that we can access the global ide
// from within other parts of the angular app.
App.factory('ide', [
'$http',
'queuedHttp',
'$modal',
'$q',
'$filter',
'$timeout',
function($http, queuedHttp, $modal, $q, $filter, $timeout) {
const ide = {}
ide.$http = $http
ide.queuedHttp = queuedHttp
ide.$q = $q
ide.$filter = $filter
ide.$timeout = $timeout
App.factory('ide', function(
$http,
queuedHttp,
$modal,
$q,
$filter,
$timeout
) {
const ide = {}
ide.$http = $http
ide.queuedHttp = queuedHttp
ide.$q = $q
ide.$filter = $filter
ide.$timeout = $timeout
this.recentEvents = []
ide.pushEvent = (type, meta) => {
if (meta == null) {
meta = {}
}
sl_console.log('event', type, meta)
this.recentEvents.push({ type, meta, date: new Date() })
if (this.recentEvents.length > 100) {
return this.recentEvents.shift()
}
this.recentEvents = []
ide.pushEvent = (type, meta) => {
if (meta == null) {
meta = {}
}
sl_console.log('event', type, meta)
this.recentEvents.push({ type, meta, date: new Date() })
if (this.recentEvents.length > 100) {
return this.recentEvents.shift()
}
}
ide.reportError = (error, meta) => {
if (meta == null) {
meta = {}
}
meta.client_id = __guard__(
ide.reportError = (error, meta) => {
if (meta == null) {
meta = {}
}
meta.client_id = __guard__(
this.socket != null ? this.socket.socket : undefined,
x => x.sessionid
)
meta.transport = __guard__(
__guard__(
this.socket != null ? this.socket.socket : undefined,
x => x.sessionid
)
meta.transport = __guard__(
__guard__(
this.socket != null ? this.socket.socket : undefined,
x2 => x2.transport
),
x1 => x1.name
)
meta.client_now = new Date()
meta.recent_events = this.recentEvents
const errorObj = {}
if (typeof error === 'object') {
for (let key of Array.from(Object.getOwnPropertyNames(error))) {
errorObj[key] = error[key]
}
} else if (typeof error === 'string') {
errorObj.message = error
x2 => x2.transport
),
x1 => x1.name
)
meta.client_now = new Date()
meta.recent_events = this.recentEvents
const errorObj = {}
if (typeof error === 'object') {
for (let key of Array.from(Object.getOwnPropertyNames(error))) {
errorObj[key] = error[key]
}
return $http.post('/error/client', {
error: errorObj,
meta,
_csrf: window.csrfToken
})
} else if (typeof error === 'string') {
errorObj.message = error
}
return $http.post('/error/client', {
error: errorObj,
meta,
_csrf: window.csrfToken
})
}
ide.showGenericMessageModal = (title, message) =>
$modal.open({
templateUrl: 'genericMessageModalTemplate',
controller: 'GenericMessageModalController',
resolve: {
title() {
return title
},
message() {
return message
}
}
})
ide.showLockEditorMessageModal = (title, message) =>
// modal to block the editor when connection is down
$modal.open({
templateUrl: 'lockEditorModalTemplate',
controller: 'GenericMessageModalController',
backdrop: 'static', // prevent dismiss by click on background
keyboard: false, // prevent dismiss via keyboard
resolve: {
title() {
return title
},
message() {
return message
}
ide.showGenericMessageModal = (title, message) =>
$modal.open({
templateUrl: 'genericMessageModalTemplate',
controller: 'GenericMessageModalController',
resolve: {
title() {
return title
},
windowClass: 'lock-editor-modal'
})
message() {
return message
}
}
})
return ide
}
])
ide.showLockEditorMessageModal = (title, message) =>
// modal to block the editor when connection is down
$modal.open({
templateUrl: 'lockEditorModalTemplate',
controller: 'GenericMessageModalController',
backdrop: 'static', // prevent dismiss by click on background
keyboard: false, // prevent dismiss via keyboard
resolve: {
title() {
return title
},
message() {
return message
}
},
windowClass: 'lock-editor-modal'
})
return App.controller('GenericMessageModalController', [
'$scope',
'$modalInstance',
'title',
'message',
function($scope, $modalInstance, title, message) {
$scope.title = title
$scope.message = message
return ide
})
return ($scope.done = () => $modalInstance.close())
}
])
return App.controller('GenericMessageModalController', function(
$scope,
$modalInstance,
title,
message
) {
$scope.title = title
$scope.message = message
return ($scope.done = () => $modalInstance.close())
})
})
function __guard__(value, transform) {

View file

@ -14,68 +14,67 @@
*/
define(['base'], function(App) {
const MAX_PROJECT_NAME_LENGTH = 150
return App.controller('ProjectNameController', [
'$scope',
'$element',
'settings',
'ide',
function($scope, $element, settings, ide) {
const projectNameReadOnlyEl = $element.find('.name')[0]
return App.controller('ProjectNameController', function(
$scope,
$element,
settings,
ide
) {
const projectNameReadOnlyEl = $element.find('.name')[0]
$scope.state = {
renaming: false,
overflowed: false
}
$scope.inputs = {}
$scope.startRenaming = function() {
$scope.inputs.name = $scope.project.name
$scope.state.renaming = true
return $scope.$emit('project:rename:start')
}
$scope.finishRenaming = function() {
$scope.state.renaming = false
const newName = $scope.inputs.name
if ($scope.project.name === newName) {
return
}
const oldName = $scope.project.name
$scope.project.name = newName
return settings
.saveProjectSettings({ name: $scope.project.name })
.catch(function(response) {
const { data, status } = response
$scope.project.name = oldName
if (status === 400) {
return ide.showGenericMessageModal('Error renaming project', data)
} else {
return ide.showGenericMessageModal(
'Error renaming project',
'Please try again in a moment'
)
}
})
}
ide.socket.on('projectNameUpdated', name =>
$scope.$apply(() => ($scope.project.name = name))
)
return $scope.$watch('project.name', function(name) {
if (name != null) {
window.document.title =
name + ` - Online LaTeX Editor ${ExposedSettings.appName}`
return $scope.$applyAsync(
() =>
// This ensures that the element is measured *after* the binding is done (i.e. project name is rendered).
($scope.state.overflowed =
projectNameReadOnlyEl.scrollWidth >
projectNameReadOnlyEl.clientWidth)
)
}
})
$scope.state = {
renaming: false,
overflowed: false
}
])
$scope.inputs = {}
$scope.startRenaming = function() {
$scope.inputs.name = $scope.project.name
$scope.state.renaming = true
return $scope.$emit('project:rename:start')
}
$scope.finishRenaming = function() {
$scope.state.renaming = false
const newName = $scope.inputs.name
if ($scope.project.name === newName) {
return
}
const oldName = $scope.project.name
$scope.project.name = newName
return settings
.saveProjectSettings({ name: $scope.project.name })
.catch(function(response) {
const { data, status } = response
$scope.project.name = oldName
if (status === 400) {
return ide.showGenericMessageModal('Error renaming project', data)
} else {
return ide.showGenericMessageModal(
'Error renaming project',
'Please try again in a moment'
)
}
})
}
ide.socket.on('projectNameUpdated', name =>
$scope.$apply(() => ($scope.project.name = name))
)
return $scope.$watch('project.name', function(name) {
if (name != null) {
window.document.title =
name + ` - Online LaTeX Editor ${ExposedSettings.appName}`
return $scope.$applyAsync(
() =>
// This ensures that the element is measured *after* the binding is done (i.e. project name is rendered).
($scope.state.overflowed =
projectNameReadOnlyEl.scrollWidth >
projectNameReadOnlyEl.clientWidth)
)
}
})
})
})

View file

@ -13,206 +13,194 @@
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
*/
define(['base'], App =>
App.controller('SettingsController', [
'$scope',
'settings',
'ide',
'_',
function($scope, settings, ide, _) {
$scope.overallThemesList = window.overallThemes
$scope.ui = { loadingStyleSheet: false }
App.controller('SettingsController', function($scope, settings, ide, _) {
$scope.overallThemesList = window.overallThemes
$scope.ui = { loadingStyleSheet: false }
const _updateCSSFile = function(theme) {
$scope.ui.loadingStyleSheet = true
const docHeadEl = document.querySelector('head')
const oldStyleSheetEl = document.getElementById('main-stylesheet')
const newStyleSheetEl = document.createElement('link')
newStyleSheetEl.addEventListener('load', e => {
return $scope.$applyAsync(() => {
$scope.ui.loadingStyleSheet = false
return docHeadEl.removeChild(oldStyleSheetEl)
})
const _updateCSSFile = function(theme) {
$scope.ui.loadingStyleSheet = true
const docHeadEl = document.querySelector('head')
const oldStyleSheetEl = document.getElementById('main-stylesheet')
const newStyleSheetEl = document.createElement('link')
newStyleSheetEl.addEventListener('load', e => {
return $scope.$applyAsync(() => {
$scope.ui.loadingStyleSheet = false
return docHeadEl.removeChild(oldStyleSheetEl)
})
newStyleSheetEl.setAttribute('rel', 'stylesheet')
newStyleSheetEl.setAttribute('id', 'main-stylesheet')
newStyleSheetEl.setAttribute('href', theme.path)
return docHeadEl.appendChild(newStyleSheetEl)
}
if (!['default', 'vim', 'emacs'].includes($scope.settings.mode)) {
$scope.settings.mode = 'default'
}
if (!['pdfjs', 'native'].includes($scope.settings.pdfViewer)) {
$scope.settings.pdfViewer = 'pdfjs'
}
if (
$scope.settings.fontFamily != null &&
!['monaco', 'lucida'].includes($scope.settings.fontFamily)
) {
delete $scope.settings.fontFamily
}
if (
$scope.settings.lineHeight != null &&
!['compact', 'normal', 'wide'].includes($scope.settings.lineHeight)
) {
delete $scope.settings.lineHeight
}
$scope.fontSizeAsStr = function(newVal) {
if (newVal != null) {
$scope.settings.fontSize = newVal
}
return $scope.settings.fontSize.toString()
}
$scope.$watch('settings.editorTheme', (editorTheme, oldEditorTheme) => {
if (editorTheme !== oldEditorTheme) {
return settings.saveSettings({ editorTheme })
}
})
$scope.$watch(
'settings.overallTheme',
(overallTheme, oldOverallTheme) => {
if (overallTheme !== oldOverallTheme) {
const chosenTheme = _.find(
$scope.overallThemesList,
theme => theme.val === overallTheme
)
if (chosenTheme != null) {
_updateCSSFile(chosenTheme)
return settings.saveSettings({ overallTheme })
}
}
}
)
$scope.$watch('settings.fontSize', (fontSize, oldFontSize) => {
if (fontSize !== oldFontSize) {
return settings.saveSettings({ fontSize: parseInt(fontSize, 10) })
}
})
$scope.$watch('settings.mode', (mode, oldMode) => {
if (mode !== oldMode) {
return settings.saveSettings({ mode })
}
})
$scope.$watch(
'settings.autoComplete',
(autoComplete, oldAutoComplete) => {
if (autoComplete !== oldAutoComplete) {
return settings.saveSettings({ autoComplete })
}
}
)
$scope.$watch(
'settings.autoPairDelimiters',
(autoPairDelimiters, oldAutoPairDelimiters) => {
if (autoPairDelimiters !== oldAutoPairDelimiters) {
return settings.saveSettings({ autoPairDelimiters })
}
}
)
$scope.$watch('settings.pdfViewer', (pdfViewer, oldPdfViewer) => {
if (pdfViewer !== oldPdfViewer) {
return settings.saveSettings({ pdfViewer })
}
})
$scope.$watch(
'settings.syntaxValidation',
(syntaxValidation, oldSyntaxValidation) => {
if (syntaxValidation !== oldSyntaxValidation) {
return settings.saveSettings({ syntaxValidation })
}
}
)
$scope.$watch('settings.fontFamily', (fontFamily, oldFontFamily) => {
if (fontFamily !== oldFontFamily) {
return settings.saveSettings({ fontFamily })
}
})
$scope.$watch('settings.lineHeight', (lineHeight, oldLineHeight) => {
if (lineHeight !== oldLineHeight) {
return settings.saveSettings({ lineHeight })
}
})
$scope.$watch('project.spellCheckLanguage', (language, oldLanguage) => {
if (this.ignoreUpdates) {
return
}
if (oldLanguage != null && language !== oldLanguage) {
settings.saveProjectSettings({ spellCheckLanguage: language })
// Also set it as the default for the user
return settings.saveSettings({ spellCheckLanguage: language })
}
})
$scope.$watch('project.compiler', (compiler, oldCompiler) => {
if (this.ignoreUpdates) {
return
}
if (oldCompiler != null && compiler !== oldCompiler) {
return settings.saveProjectSettings({ compiler })
}
})
$scope.$watch('project.imageName', (imageName, oldImageName) => {
if (this.ignoreUpdates) {
return
}
if (oldImageName != null && imageName !== oldImageName) {
return settings.saveProjectSettings({ imageName })
}
})
$scope.$watch('project.rootDoc_id', (rootDoc_id, oldRootDoc_id) => {
if (this.ignoreUpdates) {
return
}
// don't save on initialisation, Angular passes oldRootDoc_id as
// undefined in this case.
if (typeof oldRootDoc_id === 'undefined') {
return
}
// otherwise only save changes, null values are allowed
if (rootDoc_id !== oldRootDoc_id) {
return settings.saveProjectSettings({ rootDocId: rootDoc_id })
}
})
ide.socket.on('compilerUpdated', compiler => {
this.ignoreUpdates = true
$scope.$apply(() => {
return ($scope.project.compiler = compiler)
})
return delete this.ignoreUpdates
})
ide.socket.on('imageNameUpdated', imageName => {
this.ignoreUpdates = true
$scope.$apply(() => {
return ($scope.project.imageName = imageName)
})
return delete this.ignoreUpdates
})
return ide.socket.on('spellCheckLanguageUpdated', languageCode => {
this.ignoreUpdates = true
$scope.$apply(() => {
return ($scope.project.spellCheckLanguage = languageCode)
})
return delete this.ignoreUpdates
})
newStyleSheetEl.setAttribute('rel', 'stylesheet')
newStyleSheetEl.setAttribute('id', 'main-stylesheet')
newStyleSheetEl.setAttribute('href', theme.path)
return docHeadEl.appendChild(newStyleSheetEl)
}
]))
if (!['default', 'vim', 'emacs'].includes($scope.settings.mode)) {
$scope.settings.mode = 'default'
}
if (!['pdfjs', 'native'].includes($scope.settings.pdfViewer)) {
$scope.settings.pdfViewer = 'pdfjs'
}
if (
$scope.settings.fontFamily != null &&
!['monaco', 'lucida'].includes($scope.settings.fontFamily)
) {
delete $scope.settings.fontFamily
}
if (
$scope.settings.lineHeight != null &&
!['compact', 'normal', 'wide'].includes($scope.settings.lineHeight)
) {
delete $scope.settings.lineHeight
}
$scope.fontSizeAsStr = function(newVal) {
if (newVal != null) {
$scope.settings.fontSize = newVal
}
return $scope.settings.fontSize.toString()
}
$scope.$watch('settings.editorTheme', (editorTheme, oldEditorTheme) => {
if (editorTheme !== oldEditorTheme) {
return settings.saveSettings({ editorTheme })
}
})
$scope.$watch('settings.overallTheme', (overallTheme, oldOverallTheme) => {
if (overallTheme !== oldOverallTheme) {
const chosenTheme = _.find(
$scope.overallThemesList,
theme => theme.val === overallTheme
)
if (chosenTheme != null) {
_updateCSSFile(chosenTheme)
return settings.saveSettings({ overallTheme })
}
}
})
$scope.$watch('settings.fontSize', (fontSize, oldFontSize) => {
if (fontSize !== oldFontSize) {
return settings.saveSettings({ fontSize: parseInt(fontSize, 10) })
}
})
$scope.$watch('settings.mode', (mode, oldMode) => {
if (mode !== oldMode) {
return settings.saveSettings({ mode })
}
})
$scope.$watch('settings.autoComplete', (autoComplete, oldAutoComplete) => {
if (autoComplete !== oldAutoComplete) {
return settings.saveSettings({ autoComplete })
}
})
$scope.$watch(
'settings.autoPairDelimiters',
(autoPairDelimiters, oldAutoPairDelimiters) => {
if (autoPairDelimiters !== oldAutoPairDelimiters) {
return settings.saveSettings({ autoPairDelimiters })
}
}
)
$scope.$watch('settings.pdfViewer', (pdfViewer, oldPdfViewer) => {
if (pdfViewer !== oldPdfViewer) {
return settings.saveSettings({ pdfViewer })
}
})
$scope.$watch(
'settings.syntaxValidation',
(syntaxValidation, oldSyntaxValidation) => {
if (syntaxValidation !== oldSyntaxValidation) {
return settings.saveSettings({ syntaxValidation })
}
}
)
$scope.$watch('settings.fontFamily', (fontFamily, oldFontFamily) => {
if (fontFamily !== oldFontFamily) {
return settings.saveSettings({ fontFamily })
}
})
$scope.$watch('settings.lineHeight', (lineHeight, oldLineHeight) => {
if (lineHeight !== oldLineHeight) {
return settings.saveSettings({ lineHeight })
}
})
$scope.$watch('project.spellCheckLanguage', (language, oldLanguage) => {
if (this.ignoreUpdates) {
return
}
if (oldLanguage != null && language !== oldLanguage) {
settings.saveProjectSettings({ spellCheckLanguage: language })
// Also set it as the default for the user
return settings.saveSettings({ spellCheckLanguage: language })
}
})
$scope.$watch('project.compiler', (compiler, oldCompiler) => {
if (this.ignoreUpdates) {
return
}
if (oldCompiler != null && compiler !== oldCompiler) {
return settings.saveProjectSettings({ compiler })
}
})
$scope.$watch('project.imageName', (imageName, oldImageName) => {
if (this.ignoreUpdates) {
return
}
if (oldImageName != null && imageName !== oldImageName) {
return settings.saveProjectSettings({ imageName })
}
})
$scope.$watch('project.rootDoc_id', (rootDoc_id, oldRootDoc_id) => {
if (this.ignoreUpdates) {
return
}
// don't save on initialisation, Angular passes oldRootDoc_id as
// undefined in this case.
if (typeof oldRootDoc_id === 'undefined') {
return
}
// otherwise only save changes, null values are allowed
if (rootDoc_id !== oldRootDoc_id) {
return settings.saveProjectSettings({ rootDocId: rootDoc_id })
}
})
ide.socket.on('compilerUpdated', compiler => {
this.ignoreUpdates = true
$scope.$apply(() => {
return ($scope.project.compiler = compiler)
})
return delete this.ignoreUpdates
})
ide.socket.on('imageNameUpdated', imageName => {
this.ignoreUpdates = true
$scope.$apply(() => {
return ($scope.project.imageName = imageName)
})
return delete this.ignoreUpdates
})
return ide.socket.on('spellCheckLanguageUpdated', languageCode => {
this.ignoreUpdates = true
$scope.$apply(() => {
return ($scope.project.spellCheckLanguage = languageCode)
})
return delete this.ignoreUpdates
})
}))

View file

@ -12,56 +12,52 @@
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
*/
define(['base'], App =>
App.factory('settings', [
'ide',
'event_tracking',
(ide, event_tracking) => ({
saveSettings(data) {
// Tracking code.
for (let key of Array.from(Object.keys(data))) {
const changedSetting = key
const changedSettingVal = data[key]
event_tracking.sendMB('setting-changed', {
changedSetting,
changedSettingVal
})
}
// End of tracking code.
data._csrf = window.csrfToken
return ide.$http.post('/user/settings', data)
},
saveProjectSettings(data) {
// Tracking code.
for (let key of Array.from(Object.keys(data))) {
const changedSetting = key
const changedSettingVal = data[key]
event_tracking.sendMB('project-setting-changed', {
changedSetting,
changedSettingVal
})
}
// End of tracking code.
data._csrf = window.csrfToken
return ide.$http.post(`/project/${ide.project_id}/settings`, data)
},
saveProjectAdminSettings(data) {
// Tracking code.
for (let key of Array.from(Object.keys(data))) {
const changedSetting = key
const changedSettingVal = data[key]
event_tracking.sendMB('project-admin-setting-changed', {
changedSetting,
changedSettingVal
})
}
// End of tracking code.
data._csrf = window.csrfToken
return ide.$http.post(`/project/${ide.project_id}/settings/admin`, data)
App.factory('settings', (ide, event_tracking) => ({
saveSettings(data) {
// Tracking code.
for (let key of Array.from(Object.keys(data))) {
const changedSetting = key
const changedSettingVal = data[key]
event_tracking.sendMB('setting-changed', {
changedSetting,
changedSettingVal
})
}
})
]))
// End of tracking code.
data._csrf = window.csrfToken
return ide.$http.post('/user/settings', data)
},
saveProjectSettings(data) {
// Tracking code.
for (let key of Array.from(Object.keys(data))) {
const changedSetting = key
const changedSettingVal = data[key]
event_tracking.sendMB('project-setting-changed', {
changedSetting,
changedSettingVal
})
}
// End of tracking code.
data._csrf = window.csrfToken
return ide.$http.post(`/project/${ide.project_id}/settings`, data)
},
saveProjectAdminSettings(data) {
// Tracking code.
for (let key of Array.from(Object.keys(data))) {
const changedSetting = key
const changedSettingVal = data[key]
event_tracking.sendMB('project-admin-setting-changed', {
changedSetting,
changedSettingVal
})
}
// End of tracking code.
data._csrf = window.csrfToken
return ide.$http.post(`/project/${ide.project_id}/settings/admin`, data)
}
})))

View file

@ -13,66 +13,58 @@
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
*/
define(['base'], App =>
App.controller('ShareController', [
'$scope',
'$modal',
'ide',
'projectInvites',
'projectMembers',
'event_tracking',
function(
$scope,
$modal,
ide,
projectInvites,
projectMembers,
event_tracking
) {
$scope.openShareProjectModal = function(isAdmin) {
$scope.isAdmin = isAdmin
event_tracking.sendMBOnce('ide-open-share-modal-once')
App.controller('ShareController', function(
$scope,
$modal,
ide,
projectInvites,
projectMembers,
event_tracking
) {
$scope.openShareProjectModal = function(isAdmin) {
$scope.isAdmin = isAdmin
event_tracking.sendMBOnce('ide-open-share-modal-once')
return $modal.open({
templateUrl: 'shareProjectModalTemplate',
controller: 'ShareProjectModalController',
scope: $scope
})
}
ide.socket.on('project:tokens:changed', data => {
if (data.tokens != null) {
ide.$scope.project.tokens = data.tokens
return $scope.$digest()
}
})
return ide.socket.on('project:membership:changed', data => {
if (data.members) {
projectMembers
.getMembers()
.then(response => {
;({ data } = response)
if (data.members) {
return ($scope.project.members = data.members)
}
})
.catch(() => {
return console.error('Error fetching members for project')
})
}
if (data.invites) {
return projectInvites
.getInvites()
.then(response => {
;({ data } = response)
if (data.invites) {
return ($scope.project.invites = data.invites)
}
})
.catch(() => {
return console.error('Error fetching invites for project')
})
}
return $modal.open({
templateUrl: 'shareProjectModalTemplate',
controller: 'ShareProjectModalController',
scope: $scope
})
}
]))
ide.socket.on('project:tokens:changed', data => {
if (data.tokens != null) {
ide.$scope.project.tokens = data.tokens
return $scope.$digest()
}
})
return ide.socket.on('project:membership:changed', data => {
if (data.members) {
projectMembers
.getMembers()
.then(response => {
;({ data } = response)
if (data.members) {
return ($scope.project.members = data.members)
}
})
.catch(() => {
return console.error('Error fetching members for project')
})
}
if (data.invites) {
return projectInvites
.getInvites()
.then(response => {
;({ data } = response)
if (data.invites) {
return ($scope.project.invites = data.invites)
}
})
.catch(() => {
return console.error('Error fetching invites for project')
})
}
})
}))

View file

@ -10,45 +10,41 @@
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
*/
define(['base'], App =>
App.factory('projectInvites', [
'ide',
'$http',
(ide, $http) => ({
sendInvite(email, privileges, grecaptchaResponse) {
return $http.post(`/project/${ide.project_id}/invite`, {
email,
privileges,
_csrf: window.csrfToken,
'g-recaptcha-response': grecaptchaResponse
})
},
App.factory('projectInvites', (ide, $http) => ({
sendInvite(email, privileges, grecaptchaResponse) {
return $http.post(`/project/${ide.project_id}/invite`, {
email,
privileges,
_csrf: window.csrfToken,
'g-recaptcha-response': grecaptchaResponse
})
},
revokeInvite(inviteId) {
return $http({
url: `/project/${ide.project_id}/invite/${inviteId}`,
method: 'DELETE',
headers: {
'X-Csrf-Token': window.csrfToken
}
})
},
revokeInvite(inviteId) {
return $http({
url: `/project/${ide.project_id}/invite/${inviteId}`,
method: 'DELETE',
headers: {
'X-Csrf-Token': window.csrfToken
}
})
},
resendInvite(inviteId, privileges) {
return $http.post(
`/project/${ide.project_id}/invite/${inviteId}/resend`,
{
_csrf: window.csrfToken
}
)
},
resendInvite(inviteId, privileges) {
return $http.post(
`/project/${ide.project_id}/invite/${inviteId}/resend`,
{
_csrf: window.csrfToken
}
)
},
getInvites() {
return $http.get(`/project/${ide.project_id}/invites`, {
json: true,
headers: {
'X-Csrf-Token': window.csrfToken
}
})
}
})
]))
getInvites() {
return $http.get(`/project/${ide.project_id}/invites`, {
json: true,
headers: {
'X-Csrf-Token': window.csrfToken
}
})
}
})))

View file

@ -10,35 +10,31 @@
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
*/
define(['base'], App =>
App.factory('projectMembers', [
'ide',
'$http',
(ide, $http) => ({
removeMember(member) {
return $http({
url: `/project/${ide.project_id}/users/${member._id}`,
method: 'DELETE',
headers: {
'X-Csrf-Token': window.csrfToken
}
})
},
App.factory('projectMembers', (ide, $http) => ({
removeMember(member) {
return $http({
url: `/project/${ide.project_id}/users/${member._id}`,
method: 'DELETE',
headers: {
'X-Csrf-Token': window.csrfToken
}
})
},
addGroup(group_id, privileges) {
return $http.post(`/project/${ide.project_id}/group`, {
group_id,
privileges,
_csrf: window.csrfToken
})
},
addGroup(group_id, privileges) {
return $http.post(`/project/${ide.project_id}/group`, {
group_id,
privileges,
_csrf: window.csrfToken
})
},
getMembers() {
return $http.get(`/project/${ide.project_id}/members`, {
json: true,
headers: {
'X-Csrf-Token': window.csrfToken
}
})
}
})
]))
getMembers() {
return $http.get(`/project/${ide.project_id}/members`, {
json: true,
headers: {
'X-Csrf-Token': window.csrfToken
}
})
}
})))

View file

@ -14,129 +14,120 @@
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
*/
define(['base'], function(App) {
App.controller('AccountSettingsController', [
'$scope',
'$http',
'$modal',
'event_tracking',
'UserAffiliationsDataService',
'UserOauthDataService',
function(
$scope,
$http,
$modal,
event_tracking,
UserAffiliationsDataService,
UserOauthDataService
) {
$scope.subscribed = true
App.controller('AccountSettingsController', function(
$scope,
$http,
$modal,
event_tracking,
UserAffiliationsDataService,
UserOauthDataService
) {
$scope.subscribed = true
$scope.unsubscribe = function() {
$scope.unsubscribing = true
return $http({
method: 'DELETE',
url: '/user/newsletter/unsubscribe',
headers: {
'X-CSRF-Token': window.csrfToken
$scope.unsubscribe = function() {
$scope.unsubscribing = true
return $http({
method: 'DELETE',
url: '/user/newsletter/unsubscribe',
headers: {
'X-CSRF-Token': window.csrfToken
}
})
.then(function() {
$scope.unsubscribing = false
return ($scope.subscribed = false)
})
.catch(() => ($scope.unsubscribing = true))
}
$scope.deleteAccount = function() {
let modalInstance
return (modalInstance = $modal.open({
templateUrl: 'deleteAccountModalTemplate',
controller: 'DeleteAccountModalController',
resolve: {
userDefaultEmail() {
return UserAffiliationsDataService.getUserDefaultEmail()
.then(
defaultEmailDetails =>
(defaultEmailDetails != null
? defaultEmailDetails.email
: undefined) || null
)
.catch(() => null)
}
}
}))
}
return ($scope.upgradeIntegration = service =>
event_tracking.send('subscription-funnel', 'settings-page', service))
})
return App.controller('DeleteAccountModalController', function(
$scope,
$modalInstance,
$timeout,
$http,
userDefaultEmail
) {
$scope.state = {
isValid: false,
deleteText: '',
password: '',
confirmV1Purge: false,
confirmSharelatexDelete: false,
inflight: false,
error: null
}
$scope.userDefaultEmail = userDefaultEmail
$modalInstance.opened.then(() =>
$timeout(() => $scope.$broadcast('open'), 700)
)
$scope.checkValidation = () =>
($scope.state.isValid =
userDefaultEmail != null &&
$scope.state.deleteText.toLowerCase() ===
userDefaultEmail.toLowerCase() &&
$scope.state.password.length > 0 &&
$scope.state.confirmV1Purge &&
$scope.state.confirmSharelatexDelete)
$scope.delete = function() {
$scope.state.inflight = true
$scope.state.error = null
return $http({
method: 'POST',
url: '/user/delete',
headers: {
'X-CSRF-Token': window.csrfToken,
'Content-Type': 'application/json'
},
data: {
password: $scope.state.password
},
disableAutoLoginRedirect: true // we want to handle errors ourselves
})
.then(function() {
$modalInstance.close()
$scope.state.inflight = false
$scope.state.error = null
return setTimeout(() => (window.location = '/login'), 1000)
})
.catch(function(response) {
const { data, status } = response
$scope.state.inflight = false
if (status === 403) {
$scope.state.error = { code: 'InvalidCredentialsError' }
} else {
$scope.state.error = { code: data.error }
}
})
.then(function() {
$scope.unsubscribing = false
return ($scope.subscribed = false)
})
.catch(() => ($scope.unsubscribing = true))
}
$scope.deleteAccount = function() {
let modalInstance
return (modalInstance = $modal.open({
templateUrl: 'deleteAccountModalTemplate',
controller: 'DeleteAccountModalController',
resolve: {
userDefaultEmail() {
return UserAffiliationsDataService.getUserDefaultEmail()
.then(
defaultEmailDetails =>
(defaultEmailDetails != null
? defaultEmailDetails.email
: undefined) || null
)
.catch(() => null)
}
}
}))
}
return ($scope.upgradeIntegration = service =>
event_tracking.send('subscription-funnel', 'settings-page', service))
}
])
return App.controller('DeleteAccountModalController', [
'$scope',
'$modalInstance',
'$timeout',
'$http',
'userDefaultEmail',
function($scope, $modalInstance, $timeout, $http, userDefaultEmail) {
$scope.state = {
isValid: false,
deleteText: '',
password: '',
confirmV1Purge: false,
confirmSharelatexDelete: false,
inflight: false,
error: null
}
$scope.userDefaultEmail = userDefaultEmail
$modalInstance.opened.then(() =>
$timeout(() => $scope.$broadcast('open'), 700)
)
$scope.checkValidation = () =>
($scope.state.isValid =
userDefaultEmail != null &&
$scope.state.deleteText.toLowerCase() ===
userDefaultEmail.toLowerCase() &&
$scope.state.password.length > 0 &&
$scope.state.confirmV1Purge &&
$scope.state.confirmSharelatexDelete)
$scope.delete = function() {
$scope.state.inflight = true
$scope.state.error = null
return $http({
method: 'POST',
url: '/user/delete',
headers: {
'X-CSRF-Token': window.csrfToken,
'Content-Type': 'application/json'
},
data: {
password: $scope.state.password
},
disableAutoLoginRedirect: true // we want to handle errors ourselves
})
.then(function() {
$modalInstance.close()
$scope.state.inflight = false
$scope.state.error = null
return setTimeout(() => (window.location = '/login'), 1000)
})
.catch(function(response) {
const { data, status } = response
$scope.state.inflight = false
if (status === 403) {
$scope.state.error = { code: 'InvalidCredentialsError' }
} else {
$scope.state.error = { code: data.error }
}
})
}
return ($scope.cancel = () => $modalInstance.dismiss('cancel'))
}
])
return ($scope.cancel = () => $modalInstance.dismiss('cancel'))
})
})

View file

@ -15,250 +15,249 @@
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
*/
define(['base'], App =>
App.controller('UserAffiliationsController', [
'$scope',
'UserAffiliationsDataService',
'$q',
'_',
function($scope, UserAffiliationsDataService, $q, _) {
$scope.userEmails = []
App.controller('UserAffiliationsController', function(
$scope,
UserAffiliationsDataService,
$q,
_
) {
$scope.userEmails = []
const LOCAL_AND_DOMAIN_REGEX = /([^@]+)@(.+)/
const EMAIL_REGEX = /^(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|(\ ".+\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA -Z\-0-9]+\.)+[a-zA-Z]{2,}))$/
const LOCAL_AND_DOMAIN_REGEX = /([^@]+)@(.+)/
const EMAIL_REGEX = /^(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|(\ ".+\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA -Z\-0-9]+\.)+[a-zA-Z]{2,}))$/
const _matchLocalAndDomain = function(userEmailInput) {
const match =
userEmailInput != null
? userEmailInput.match(LOCAL_AND_DOMAIN_REGEX)
: undefined
if (match != null) {
return { local: match[1], domain: match[2] }
} else {
return { local: null, domain: null }
}
const _matchLocalAndDomain = function(userEmailInput) {
const match =
userEmailInput != null
? userEmailInput.match(LOCAL_AND_DOMAIN_REGEX)
: undefined
if (match != null) {
return { local: match[1], domain: match[2] }
} else {
return { local: null, domain: null }
}
}
$scope.getEmailSuggestion = function(userInput) {
const userInputLocalAndDomain = _matchLocalAndDomain(userInput)
$scope.ui.isValidEmail = EMAIL_REGEX.test(userInput)
$scope.ui.isBlacklistedEmail = false
$scope.ui.showManualUniversitySelectionUI = false
if (userInputLocalAndDomain.domain != null) {
$scope.ui.isBlacklistedEmail = UserAffiliationsDataService.isDomainBlacklisted(
userInputLocalAndDomain.domain
)
return UserAffiliationsDataService.getUniversityDomainFromPartialDomainInput(
userInputLocalAndDomain.domain
)
.then(function(universityDomain) {
const currentUserInputLocalAndDomain = _matchLocalAndDomain(
$scope.newAffiliation.email
)
if (
currentUserInputLocalAndDomain.domain ===
universityDomain.hostname
) {
$scope.newAffiliation.university = universityDomain.university
$scope.newAffiliation.department = universityDomain.department
} else {
$scope.newAffiliation.university = null
$scope.newAffiliation.department = null
}
return $q.resolve(
`${userInputLocalAndDomain.local}@${universityDomain.hostname}`
)
})
.catch(function() {
$scope.getEmailSuggestion = function(userInput) {
const userInputLocalAndDomain = _matchLocalAndDomain(userInput)
$scope.ui.isValidEmail = EMAIL_REGEX.test(userInput)
$scope.ui.isBlacklistedEmail = false
$scope.ui.showManualUniversitySelectionUI = false
if (userInputLocalAndDomain.domain != null) {
$scope.ui.isBlacklistedEmail = UserAffiliationsDataService.isDomainBlacklisted(
userInputLocalAndDomain.domain
)
return UserAffiliationsDataService.getUniversityDomainFromPartialDomainInput(
userInputLocalAndDomain.domain
)
.then(function(universityDomain) {
const currentUserInputLocalAndDomain = _matchLocalAndDomain(
$scope.newAffiliation.email
)
if (
currentUserInputLocalAndDomain.domain ===
universityDomain.hostname
) {
$scope.newAffiliation.university = universityDomain.university
$scope.newAffiliation.department = universityDomain.department
} else {
$scope.newAffiliation.university = null
$scope.newAffiliation.department = null
return $q.reject(null)
})
} else {
$scope.newAffiliation.university = null
$scope.newAffiliation.department = null
return $q.reject(null)
}
}
$scope.selectUniversityManually = function() {
}
return $q.resolve(
`${userInputLocalAndDomain.local}@${universityDomain.hostname}`
)
})
.catch(function() {
$scope.newAffiliation.university = null
$scope.newAffiliation.department = null
return $q.reject(null)
})
} else {
$scope.newAffiliation.university = null
$scope.newAffiliation.department = null
return ($scope.ui.showManualUniversitySelectionUI = true)
return $q.reject(null)
}
}
$scope.changeAffiliation = function(userEmail) {
if (
__guard__(
userEmail.affiliation != null
? userEmail.affiliation.institution
: undefined,
x => x.id
) != null
) {
UserAffiliationsDataService.getUniversityDetails(
userEmail.affiliation.institution.id
).then(
universityDetails =>
($scope.affiliationToChange.university = universityDetails)
)
}
$scope.selectUniversityManually = function() {
$scope.newAffiliation.university = null
$scope.newAffiliation.department = null
return ($scope.ui.showManualUniversitySelectionUI = true)
}
$scope.affiliationToChange.email = userEmail.email
$scope.affiliationToChange.role = userEmail.affiliation.role
return ($scope.affiliationToChange.department =
userEmail.affiliation.department)
}
$scope.saveAffiliationChange = function(userEmail) {
userEmail.affiliation.role = $scope.affiliationToChange.role
userEmail.affiliation.department = $scope.affiliationToChange.department
_resetAffiliationToChange()
return _monitorRequest(
UserAffiliationsDataService.addRoleAndDepartment(
userEmail.email,
userEmail.affiliation.role,
userEmail.affiliation.department
)
).then(() => setTimeout(() => _getUserEmails()))
}
$scope.cancelAffiliationChange = email => _resetAffiliationToChange()
$scope.isChangingAffiliation = email =>
$scope.affiliationToChange.email === email
$scope.showAddEmailForm = () => ($scope.ui.showAddEmailUI = true)
$scope.addNewEmail = function() {
let addEmailPromise
if ($scope.newAffiliation.university == null) {
addEmailPromise = UserAffiliationsDataService.addUserEmail(
$scope.newAffiliation.email
)
} else {
if ($scope.newAffiliation.university.isUserSuggested) {
addEmailPromise = UserAffiliationsDataService.addUserAffiliationWithUnknownUniversity(
$scope.newAffiliation.email,
$scope.newAffiliation.university.name,
$scope.newAffiliation.country.code,
$scope.newAffiliation.role,
$scope.newAffiliation.department
)
} else {
addEmailPromise = UserAffiliationsDataService.addUserAffiliation(
$scope.newAffiliation.email,
$scope.newAffiliation.university.id,
$scope.newAffiliation.role,
$scope.newAffiliation.department
)
}
}
$scope.ui.isAddingNewEmail = true
$scope.ui.showAddEmailUI = false
return _monitorRequest(addEmailPromise)
.then(function() {
_resetNewAffiliation()
_resetAddingEmail()
return setTimeout(() => _getUserEmails())
})
.finally(() => ($scope.ui.isAddingNewEmail = false))
}
$scope.setDefaultUserEmail = userEmail =>
_monitorRequest(
UserAffiliationsDataService.setDefaultUserEmail(userEmail.email)
).then(function() {
for (let email of Array.from($scope.userEmails || [])) {
email.default = false
}
return (userEmail.default = true)
})
$scope.removeUserEmail = function(userEmail) {
$scope.userEmails = $scope.userEmails.filter(ue => ue !== userEmail)
return _monitorRequest(
UserAffiliationsDataService.removeUserEmail(userEmail.email)
$scope.changeAffiliation = function(userEmail) {
if (
__guard__(
userEmail.affiliation != null
? userEmail.affiliation.institution
: undefined,
x => x.id
) != null
) {
UserAffiliationsDataService.getUniversityDetails(
userEmail.affiliation.institution.id
).then(
universityDetails =>
($scope.affiliationToChange.university = universityDetails)
)
}
$scope.resendConfirmationEmail = function(userEmail) {
$scope.ui.isResendingConfirmation = true
return _monitorRequest(
UserAffiliationsDataService.resendConfirmationEmail(userEmail.email)
).finally(() => ($scope.ui.isResendingConfirmation = false))
}
$scope.affiliationToChange.email = userEmail.email
$scope.affiliationToChange.role = userEmail.affiliation.role
return ($scope.affiliationToChange.department =
userEmail.affiliation.department)
}
$scope.acknowledgeError = function() {
_reset()
return _getUserEmails()
}
$scope.saveAffiliationChange = function(userEmail) {
userEmail.affiliation.role = $scope.affiliationToChange.role
userEmail.affiliation.department = $scope.affiliationToChange.department
_resetAffiliationToChange()
return _monitorRequest(
UserAffiliationsDataService.addRoleAndDepartment(
userEmail.email,
userEmail.affiliation.role,
userEmail.affiliation.department
)
).then(() => setTimeout(() => _getUserEmails()))
}
var _resetAffiliationToChange = () =>
($scope.affiliationToChange = {
email: '',
university: null,
role: null,
department: null
})
$scope.cancelAffiliationChange = email => _resetAffiliationToChange()
var _resetNewAffiliation = () =>
($scope.newAffiliation = {
email: '',
country: null,
university: null,
role: null,
department: null
})
$scope.isChangingAffiliation = email =>
$scope.affiliationToChange.email === email
var _resetAddingEmail = function() {
$scope.ui.showAddEmailUI = false
$scope.ui.isValidEmail = false
$scope.ui.isBlacklistedEmail = false
return ($scope.ui.showManualUniversitySelectionUI = false)
}
$scope.showAddEmailForm = () => ($scope.ui.showAddEmailUI = true)
var _reset = function() {
$scope.ui = {
hasError: false,
errorMessage: '',
showChangeAffiliationUI: false,
isMakingRequest: false,
isLoadingEmails: false,
isAddingNewEmail: false,
isResendingConfirmation: false
$scope.addNewEmail = function() {
let addEmailPromise
if ($scope.newAffiliation.university == null) {
addEmailPromise = UserAffiliationsDataService.addUserEmail(
$scope.newAffiliation.email
)
} else {
if ($scope.newAffiliation.university.isUserSuggested) {
addEmailPromise = UserAffiliationsDataService.addUserAffiliationWithUnknownUniversity(
$scope.newAffiliation.email,
$scope.newAffiliation.university.name,
$scope.newAffiliation.country.code,
$scope.newAffiliation.role,
$scope.newAffiliation.department
)
} else {
addEmailPromise = UserAffiliationsDataService.addUserAffiliation(
$scope.newAffiliation.email,
$scope.newAffiliation.university.id,
$scope.newAffiliation.role,
$scope.newAffiliation.department
)
}
_resetAffiliationToChange()
_resetNewAffiliation()
return _resetAddingEmail()
}
$scope.ui.isAddingNewEmail = true
$scope.ui.showAddEmailUI = false
return _monitorRequest(addEmailPromise)
.then(function() {
_resetNewAffiliation()
_resetAddingEmail()
return setTimeout(() => _getUserEmails())
})
.finally(() => ($scope.ui.isAddingNewEmail = false))
}
$scope.setDefaultUserEmail = userEmail =>
_monitorRequest(
UserAffiliationsDataService.setDefaultUserEmail(userEmail.email)
).then(function() {
for (let email of Array.from($scope.userEmails || [])) {
email.default = false
}
return (userEmail.default = true)
})
$scope.removeUserEmail = function(userEmail) {
$scope.userEmails = $scope.userEmails.filter(ue => ue !== userEmail)
return _monitorRequest(
UserAffiliationsDataService.removeUserEmail(userEmail.email)
)
}
$scope.resendConfirmationEmail = function(userEmail) {
$scope.ui.isResendingConfirmation = true
return _monitorRequest(
UserAffiliationsDataService.resendConfirmationEmail(userEmail.email)
).finally(() => ($scope.ui.isResendingConfirmation = false))
}
$scope.acknowledgeError = function() {
_reset()
var _monitorRequest = function(promise) {
$scope.ui.hasError = false
$scope.ui.isMakingRequest = true
promise
.catch(function(response) {
$scope.ui.hasError = true
return ($scope.ui.errorMessage = __guard__(
response != null ? response.data : undefined,
x => x.message
))
})
.finally(() => ($scope.ui.isMakingRequest = false))
return promise
}
// Populates the emails table
var _getUserEmails = function() {
$scope.ui.isLoadingEmails = true
return _monitorRequest(UserAffiliationsDataService.getUserEmails())
.then(emails => ($scope.userEmails = emails))
.finally(() => ($scope.ui.isLoadingEmails = false))
}
return _getUserEmails()
}
]))
var _resetAffiliationToChange = () =>
($scope.affiliationToChange = {
email: '',
university: null,
role: null,
department: null
})
var _resetNewAffiliation = () =>
($scope.newAffiliation = {
email: '',
country: null,
university: null,
role: null,
department: null
})
var _resetAddingEmail = function() {
$scope.ui.showAddEmailUI = false
$scope.ui.isValidEmail = false
$scope.ui.isBlacklistedEmail = false
return ($scope.ui.showManualUniversitySelectionUI = false)
}
var _reset = function() {
$scope.ui = {
hasError: false,
errorMessage: '',
showChangeAffiliationUI: false,
isMakingRequest: false,
isLoadingEmails: false,
isAddingNewEmail: false,
isResendingConfirmation: false
}
_resetAffiliationToChange()
_resetNewAffiliation()
return _resetAddingEmail()
}
_reset()
var _monitorRequest = function(promise) {
$scope.ui.hasError = false
$scope.ui.isMakingRequest = true
promise
.catch(function(response) {
$scope.ui.hasError = true
return ($scope.ui.errorMessage = __guard__(
response != null ? response.data : undefined,
x => x.message
))
})
.finally(() => ($scope.ui.isMakingRequest = false))
return promise
}
// Populates the emails table
var _getUserEmails = function() {
$scope.ui.isLoadingEmails = true
return _monitorRequest(UserAffiliationsDataService.getUserEmails())
.then(emails => ($scope.userEmails = emails))
.finally(() => ($scope.ui.isLoadingEmails = false))
}
return _getUserEmails()
}))
function __guard__(value, transform) {
return typeof value !== 'undefined' && value !== null
? transform(value)

View file

@ -414,151 +414,145 @@ define(['base'], function(App) {
}
}
return App.factory('UserAffiliationsDataService', [
'$http',
'$q',
'_',
function($http, $q, _) {
const getCountries = () => $q.resolve(countriesList)
return App.factory('UserAffiliationsDataService', function($http, $q, _) {
const getCountries = () => $q.resolve(countriesList)
const getDefaultRoleHints = () => $q.resolve(defaultRoleHints)
const getDefaultRoleHints = () => $q.resolve(defaultRoleHints)
const getDefaultDepartmentHints = () => $q.resolve(defaultDepartmentHints)
const getDefaultDepartmentHints = () => $q.resolve(defaultDepartmentHints)
const getUserEmails = () =>
$http.get('/user/emails').then(response => response.data)
const getUserEmails = () =>
$http.get('/user/emails').then(response => response.data)
const getUserDefaultEmail = () =>
getUserEmails().then(userEmails =>
_.find(userEmails, userEmail => userEmail.default)
)
const getUserDefaultEmail = () =>
getUserEmails().then(userEmails =>
_.find(userEmails, userEmail => userEmail.default)
)
const getUniversitiesFromCountry = function(country) {
let universitiesFromCountry
if (universities[country.code] != null) {
universitiesFromCountry = universities[country.code]
} else {
universitiesFromCountry = $http
.get('/institutions/list', {
params: { country_code: country.code }
})
.then(response => (universities[country.code] = response.data))
}
return $q.resolve(universitiesFromCountry)
const getUniversitiesFromCountry = function(country) {
let universitiesFromCountry
if (universities[country.code] != null) {
universitiesFromCountry = universities[country.code]
} else {
universitiesFromCountry = $http
.get('/institutions/list', {
params: { country_code: country.code }
})
.then(response => (universities[country.code] = response.data))
}
return $q.resolve(universitiesFromCountry)
}
const getUniversityDomainFromPartialDomainInput = function(
partialDomainInput
) {
if (universitiesByDomain[partialDomainInput] != null) {
return $q.resolve(universitiesByDomain[partialDomainInput])
} else {
return $http
.get('/institutions/domains', {
params: { hostname: partialDomainInput, limit: 1 }
})
.then(function(response) {
const university = response.data[0]
if (
university != null &&
!isDomainBlacklisted(university.hostname)
) {
universitiesByDomain[university.hostname] = university
return $q.resolve(university)
} else {
return $q.reject(null)
}
})
}
}
const getUniversityDetails = universityId =>
$http
.get(`/institutions/list/${universityId}`)
.then(response => response.data)
const addUserEmail = email =>
$http.post('/user/emails', {
email,
_csrf: window.csrfToken
})
const addUserAffiliationWithUnknownUniversity = (
email,
unknownUniversityName,
unknownUniversityCountryCode,
role,
department
) =>
$http.post('/user/emails', {
email,
university: {
name: unknownUniversityName,
country_code: unknownUniversityCountryCode
},
role,
department,
_csrf: window.csrfToken
})
const addUserAffiliation = (email, universityId, role, department) =>
$http.post('/user/emails', {
email,
university: {
id: universityId
},
role,
department,
_csrf: window.csrfToken
})
const addRoleAndDepartment = (email, role, department) =>
$http.post('/user/emails/endorse', {
email,
role,
department,
_csrf: window.csrfToken
})
const setDefaultUserEmail = email =>
$http.post('/user/emails/default', {
email,
_csrf: window.csrfToken
})
const removeUserEmail = email =>
$http.post('/user/emails/delete', {
email,
_csrf: window.csrfToken
})
const resendConfirmationEmail = email =>
$http.post('/user/emails/resend_confirmation', {
email,
_csrf: window.csrfToken
})
var isDomainBlacklisted = domain =>
domain.toLowerCase() in domainsBlackList
return {
getCountries,
getDefaultRoleHints,
getDefaultDepartmentHints,
getUserEmails,
getUserDefaultEmail,
getUniversitiesFromCountry,
getUniversityDomainFromPartialDomainInput,
getUniversityDetails,
addUserEmail,
addUserAffiliationWithUnknownUniversity,
addUserAffiliation,
addRoleAndDepartment,
setDefaultUserEmail,
removeUserEmail,
resendConfirmationEmail,
isDomainBlacklisted
const getUniversityDomainFromPartialDomainInput = function(
partialDomainInput
) {
if (universitiesByDomain[partialDomainInput] != null) {
return $q.resolve(universitiesByDomain[partialDomainInput])
} else {
return $http
.get('/institutions/domains', {
params: { hostname: partialDomainInput, limit: 1 }
})
.then(function(response) {
const university = response.data[0]
if (
university != null &&
!isDomainBlacklisted(university.hostname)
) {
universitiesByDomain[university.hostname] = university
return $q.resolve(university)
} else {
return $q.reject(null)
}
})
}
}
])
const getUniversityDetails = universityId =>
$http
.get(`/institutions/list/${universityId}`)
.then(response => response.data)
const addUserEmail = email =>
$http.post('/user/emails', {
email,
_csrf: window.csrfToken
})
const addUserAffiliationWithUnknownUniversity = (
email,
unknownUniversityName,
unknownUniversityCountryCode,
role,
department
) =>
$http.post('/user/emails', {
email,
university: {
name: unknownUniversityName,
country_code: unknownUniversityCountryCode
},
role,
department,
_csrf: window.csrfToken
})
const addUserAffiliation = (email, universityId, role, department) =>
$http.post('/user/emails', {
email,
university: {
id: universityId
},
role,
department,
_csrf: window.csrfToken
})
const addRoleAndDepartment = (email, role, department) =>
$http.post('/user/emails/endorse', {
email,
role,
department,
_csrf: window.csrfToken
})
const setDefaultUserEmail = email =>
$http.post('/user/emails/default', {
email,
_csrf: window.csrfToken
})
const removeUserEmail = email =>
$http.post('/user/emails/delete', {
email,
_csrf: window.csrfToken
})
const resendConfirmationEmail = email =>
$http.post('/user/emails/resend_confirmation', {
email,
_csrf: window.csrfToken
})
var isDomainBlacklisted = domain => domain.toLowerCase() in domainsBlackList
return {
getCountries,
getDefaultRoleHints,
getDefaultDepartmentHints,
getUserEmails,
getUserDefaultEmail,
getUniversitiesFromCountry,
getUniversityDomainFromPartialDomainInput,
getUniversityDetails,
addUserEmail,
addUserAffiliationWithUnknownUniversity,
addUserAffiliation,
addRoleAndDepartment,
setDefaultUserEmail,
removeUserEmail,
resendConfirmationEmail,
isDomainBlacklisted
}
})
})

View file

@ -11,29 +11,25 @@
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
*/
define(['base'], App =>
App.controller('ClearSessionsController', [
'$scope',
'$http',
function($scope, $http) {
$scope.state = {
otherSessions: window.otherSessions,
error: false,
success: false
}
return ($scope.clearSessions = function() {
console.log('>> clearing all sessions')
return $http({
method: 'POST',
url: '/user/sessions/clear',
headers: { 'X-CSRF-Token': window.csrfToken }
})
.then(function() {
$scope.state.otherSessions = []
$scope.state.error = false
return ($scope.state.success = true)
})
.catch(() => ($scope.state.error = true))
})
App.controller('ClearSessionsController', function($scope, $http) {
$scope.state = {
otherSessions: window.otherSessions,
error: false,
success: false
}
]))
return ($scope.clearSessions = function() {
console.log('>> clearing all sessions')
return $http({
method: 'POST',
url: '/user/sessions/clear',
headers: { 'X-CSRF-Token': window.csrfToken }
})
.then(function() {
$scope.state.otherSessions = []
$scope.state.error = false
return ($scope.state.success = true)
})
.catch(() => ($scope.state.error = true))
})
}))

View file

@ -1,92 +1,91 @@
define(['base'], App =>
App.controller('UserOauthController', [
'$http',
'$scope',
'$q',
'_',
'UserOauthDataService',
function($http, $scope, $q, _, UserOauthDataService) {
const _monitorRequest = function(promise) {
$scope.ui.hasError = false
$scope.ui.isLoadingV1Ids = true
promise
.catch(response => {
$scope.ui.hasError = true
$scope.ui.errorMessage =
response && response.data && response.data.message
? response.data.message
: 'error'
})
.finally(() => {
$scope.ui.isLoadingV1Ids = false
})
return promise
App.controller('UserOauthController', function(
$http,
$scope,
$q,
_,
UserOauthDataService
) {
const _monitorRequest = function(promise) {
$scope.ui.hasError = false
$scope.ui.isLoadingV1Ids = true
promise
.catch(response => {
$scope.ui.hasError = true
$scope.ui.errorMessage =
response && response.data && response.data.message
? response.data.message
: 'error'
})
.finally(() => {
$scope.ui.isLoadingV1Ids = false
})
return promise
}
const _reset = function() {
$scope.ui = {
hasError: false,
errorMessage: '',
isLoadingV1Ids: false
}
const _reset = function() {
$scope.ui = {
$scope.providers = window.oauthProviders
$scope.thirdPartyIds = window.thirdPartyIds
// until oauthUseV2=true, we will use OAuth data via v1 DB,
// except for Collabratec, which is only writing to the v2 DB.
// $scope.v2ThirdPartyIds is required for Collabratec,
// and only until v2 is authoritative. Though, we should leave this
// until we stop double writes, in case we need to flip.
// Double writes for OAuth will stop when oauthFallback=false
$scope.v2ThirdPartyIds = window.thirdPartyIds
}
const _getUserV1OauthProviders = () => {
$scope.ui.isLoadingV1Ids = true
return _monitorRequest(UserOauthDataService.getUserOauthV1()).then(
thirdPartyIds => {
$scope.thirdPartyIds = thirdPartyIds
}
)
}
const _unlinkError = (providerId, err) => {
$scope.providers[providerId].ui.hasError = true
$scope.providers[providerId].ui.errorMessage =
err && err.data && err.data.message ? err.data.message : 'error'
}
$scope.unlink = providerId => {
if (window.ExposedSettings.isOverleaf) {
// UI
$scope.providers[providerId].ui = {
hasError: false,
errorMessage: '',
isLoadingV1Ids: false
isProcessing: true
}
$scope.providers = window.oauthProviders
$scope.thirdPartyIds = window.thirdPartyIds
// until oauthUseV2=true, we will use OAuth data via v1 DB,
// except for Collabratec, which is only writing to the v2 DB.
// $scope.v2ThirdPartyIds is required for Collabratec,
// and only until v2 is authoritative. Though, we should leave this
// until we stop double writes, in case we need to flip.
// Double writes for OAuth will stop when oauthFallback=false
$scope.v2ThirdPartyIds = window.thirdPartyIds
}
const _getUserV1OauthProviders = () => {
$scope.ui.isLoadingV1Ids = true
return _monitorRequest(UserOauthDataService.getUserOauthV1()).then(
thirdPartyIds => {
$scope.thirdPartyIds = thirdPartyIds
}
)
}
const _unlinkError = (providerId, err) => {
$scope.providers[providerId].ui.hasError = true
$scope.providers[providerId].ui.errorMessage =
err && err.data && err.data.message ? err.data.message : 'error'
}
$scope.unlink = providerId => {
if (window.ExposedSettings.isOverleaf) {
// UI
$scope.providers[providerId].ui = {
hasError: false,
isProcessing: true
}
// Data for update
const data = {
_csrf: window.csrfToken,
link: false,
providerId
}
$http
.post('/user/oauth-unlink', data)
.catch(error => {
$scope.providers[providerId].ui.isProcessing = false
_unlinkError(providerId, error)
})
.then(response => {
$scope.providers[providerId].ui.isProcessing = false
if (response.status === 200) {
$scope.thirdPartyIds[providerId] = null
// v2thirdPartyIds below can be removed post user c11n
$scope.v2ThirdPartyIds[providerId] = null
} else {
_unlinkError(providerId, response)
}
})
// Data for update
const data = {
_csrf: window.csrfToken,
link: false,
providerId
}
}
_reset()
if (!window.oauthUseV2) {
_getUserV1OauthProviders()
$http
.post('/user/oauth-unlink', data)
.catch(error => {
$scope.providers[providerId].ui.isProcessing = false
_unlinkError(providerId, error)
})
.then(response => {
$scope.providers[providerId].ui.isProcessing = false
if (response.status === 200) {
$scope.thirdPartyIds[providerId] = null
// v2thirdPartyIds below can be removed post user c11n
$scope.v2ThirdPartyIds[providerId] = null
} else {
_unlinkError(providerId, response)
}
})
}
}
]))
_reset()
if (!window.oauthUseV2) {
_getUserV1OauthProviders()
}
}))

View file

@ -1,20 +1,17 @@
define(['base'], function(App) {
return App.factory('UserOauthDataService', [
'$http',
function($http) {
const getUserOauthV1 = () => {
if (window.ExposedSettings.isOverleaf) {
return $http.get('/user/v1-oauth-uids').then(response => {
return response.data
})
} else {
return {}
}
}
return {
getUserOauthV1
return App.factory('UserOauthDataService', function($http) {
const getUserOauthV1 = () => {
if (window.ExposedSettings.isOverleaf) {
return $http.get('/user/v1-oauth-uids').then(response => {
return response.data
})
} else {
return {}
}
}
])
return {
getUserOauthV1
}
})
})

View file

@ -2,33 +2,31 @@
camelcase
*/
define(['base'], App =>
App.service('ProjectListService', [
() => ({
getOwnerName(project) {
if (project.accessLevel === 'owner') {
return 'You'
} else if (project.owner != null) {
return this.getUserName(project.owner)
} else {
return 'None'
}
},
getUserName(user) {
if (user && user._id === window.user_id) {
return 'You'
} else if (user) {
const { first_name, last_name, email } = user
if (first_name || last_name) {
return [first_name, last_name].filter(n => n != null).join(' ')
} else if (email) {
return email
} else {
return 'An Overleaf v1 User'
}
} else {
return 'None'
}
App.service('ProjectListService', () => ({
getOwnerName(project) {
if (project.accessLevel === 'owner') {
return 'You'
} else if (project.owner != null) {
return this.getUserName(project.owner)
} else {
return 'None'
}
})
]))
},
getUserName(user) {
if (user && user._id === window.user_id) {
return 'You'
} else if (user) {
const { first_name, last_name, email } = user
if (first_name || last_name) {
return [first_name, last_name].filter(n => n != null).join(' ')
} else if (email) {
return email
} else {
return 'An Overleaf v1 User'
}
} else {
return 'None'
}
}
})))

View file

@ -42,36 +42,30 @@ app.config([
// Interceptor to check auth failures in all $http requests
// http://bahmutov.calepin.co/catch-all-errors-in-angular-app.html
app.factory('unAuthHttpResponseInterceptor', [
'$q',
'$location',
($q, $location) => ({
responseError(response) {
// redirect any unauthorised or forbidden responses back to /login
//
// set disableAutoLoginRedirect:true in the http request config
// to disable this behaviour
if (
[401, 403].includes(response.status) &&
!(response.config != null
? response.config.disableAutoLoginRedirect
: undefined)
) {
// for /project urls set the ?redir parameter to come back here
// otherwise just go to the login page
if (window.location.pathname.match(/^\/project/)) {
window.location = `/login?redir=${encodeURI(
window.location.pathname
)}`
} else {
window.location = '/login'
}
app.factory('unAuthHttpResponseInterceptor', ($q, $location) => ({
responseError(response) {
// redirect any unauthorised or forbidden responses back to /login
//
// set disableAutoLoginRedirect:true in the http request config
// to disable this behaviour
if (
[401, 403].includes(response.status) &&
!(response.config != null
? response.config.disableAutoLoginRedirect
: undefined)
) {
// for /project urls set the ?redir parameter to come back here
// otherwise just go to the login page
if (window.location.pathname.match(/^\/project/)) {
window.location = `/login?redir=${encodeURI(window.location.pathname)}`
} else {
window.location = '/login'
}
// pass the response back to the original requester
return $q.reject(response)
}
})
])
// pass the response back to the original requester
return $q.reject(response)
}
}))
app.config([
'$httpProvider',