mirror of
https://github.com/overleaf/overleaf.git
synced 2024-11-21 20:47:08 -05:00
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:
parent
d7549544d6
commit
65849456d0
43 changed files with 5168 additions and 5810 deletions
|
@ -2,5 +2,6 @@
|
|||
"presets": [
|
||||
"react",
|
||||
["env", { "modules": false }]
|
||||
]
|
||||
],
|
||||
"plugins": ["angularjs-annotate"]
|
||||
}
|
|
@ -23,9 +23,6 @@ module.exports = function(grunt) {
|
|||
compile: {
|
||||
options: {
|
||||
optimize: 'uglify2',
|
||||
uglify2: {
|
||||
mangle: false
|
||||
},
|
||||
appDir: 'public/js',
|
||||
baseUrl: './',
|
||||
dir: 'public/minjs',
|
||||
|
|
1817
services/web/npm-shrinkwrap.json
generated
1817
services/web/npm-shrinkwrap.json
generated
File diff suppressed because it is too large
Load diff
|
@ -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",
|
||||
|
|
|
@ -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)
|
||||
})
|
||||
)
|
||||
}
|
||||
})))
|
||||
|
|
|
@ -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
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
})))
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}))
|
||||
|
|
|
@ -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
|
||||
})`
|
||||
}))
|
||||
}))
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
})
|
||||
]))
|
||||
}
|
||||
})))
|
||||
|
|
|
@ -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.'
|
||||
}
|
||||
})
|
||||
}))
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -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'))
|
||||
})
|
||||
})
|
||||
|
|
|
@ -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'
|
||||
)
|
||||
})
|
||||
}))
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
]))
|
||||
})
|
||||
}
|
||||
})))
|
||||
|
|
|
@ -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)
|
||||
})
|
||||
})
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
})
|
||||
})
|
||||
}))
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
})
|
||||
})
|
||||
}))
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
]))
|
||||
}))
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}))
|
||||
}))
|
||||
|
|
|
@ -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))
|
||||
}
|
||||
}
|
||||
]))
|
||||
))
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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 = [])
|
||||
}
|
||||
})
|
||||
}))
|
||||
|
|
|
@ -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>\
|
||||
`
|
||||
})
|
||||
]))
|
||||
})))
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
})
|
||||
}
|
||||
})))
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
})
|
||||
}))
|
||||
|
|
|
@ -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
|
@ -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) {
|
||||
|
|
|
@ -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)
|
||||
)
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
|
@ -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
|
||||
})
|
||||
}))
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
})))
|
||||
|
|
|
@ -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')
|
||||
})
|
||||
}
|
||||
})
|
||||
}))
|
||||
|
|
|
@ -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
|
||||
}
|
||||
})
|
||||
}
|
||||
})))
|
||||
|
|
|
@ -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
|
||||
}
|
||||
})
|
||||
}
|
||||
})))
|
||||
|
|
|
@ -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'))
|
||||
})
|
||||
})
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
}
|
||||
})
|
||||
})
|
||||
|
|
|
@ -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))
|
||||
})
|
||||
}))
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
}))
|
||||
|
|
|
@ -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
|
||||
}
|
||||
})
|
||||
})
|
||||
|
|
|
@ -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'
|
||||
}
|
||||
}
|
||||
})))
|
||||
|
|
|
@ -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',
|
||||
|
|
Loading…
Reference in a new issue