mirror of
https://github.com/overleaf/overleaf.git
synced 2024-11-29 13:03:42 -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": [
|
"presets": [
|
||||||
"react",
|
"react",
|
||||||
["env", { "modules": false }]
|
["env", { "modules": false }]
|
||||||
]
|
],
|
||||||
|
"plugins": ["angularjs-annotate"]
|
||||||
}
|
}
|
|
@ -23,9 +23,6 @@ module.exports = function(grunt) {
|
||||||
compile: {
|
compile: {
|
||||||
options: {
|
options: {
|
||||||
optimize: 'uglify2',
|
optimize: 'uglify2',
|
||||||
uglify2: {
|
|
||||||
mangle: false
|
|
||||||
},
|
|
||||||
appDir: 'public/js',
|
appDir: 'public/js',
|
||||||
baseUrl: './',
|
baseUrl: './',
|
||||||
dir: 'public/minjs',
|
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-cli": "^6.26.0",
|
||||||
"babel-core": "^6.26.0",
|
"babel-core": "^6.26.0",
|
||||||
"babel-loader": "^7.1.2",
|
"babel-loader": "^7.1.2",
|
||||||
|
"babel-plugin-angularjs-annotate": "^0.10.0",
|
||||||
"babel-preset-env": "^1.6.1",
|
"babel-preset-env": "^1.6.1",
|
||||||
"babel-preset-react": "^6.16.0",
|
"babel-preset-react": "^6.16.0",
|
||||||
"bunyan": "0.22.1",
|
"bunyan": "0.22.1",
|
||||||
|
|
|
@ -9,17 +9,15 @@
|
||||||
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
|
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
|
||||||
*/
|
*/
|
||||||
define(['base'], App =>
|
define(['base'], App =>
|
||||||
App.directive('equals', [
|
App.directive('equals', () => ({
|
||||||
() => ({
|
require: 'ngModel',
|
||||||
require: 'ngModel',
|
link(scope, elem, attrs, ctrl) {
|
||||||
link(scope, elem, attrs, ctrl) {
|
const firstField = `#${attrs.equals}`
|
||||||
const firstField = `#${attrs.equals}`
|
return elem.add(firstField).on('keyup', () =>
|
||||||
return elem.add(firstField).on('keyup', () =>
|
scope.$apply(function() {
|
||||||
scope.$apply(function() {
|
const equal = elem.val() === $(firstField).val()
|
||||||
const equal = elem.val() === $(firstField).val()
|
return ctrl.$setValidity('areEqual', equal)
|
||||||
return ctrl.$setValidity('areEqual', equal)
|
})
|
||||||
})
|
)
|
||||||
)
|
}
|
||||||
}
|
})))
|
||||||
})
|
|
||||||
]))
|
|
||||||
|
|
|
@ -35,72 +35,67 @@ const isInViewport = function(element) {
|
||||||
}
|
}
|
||||||
|
|
||||||
define(['base'], App =>
|
define(['base'], App =>
|
||||||
App.directive('eventTracking', [
|
App.directive('eventTracking', event_tracking => ({
|
||||||
'event_tracking',
|
scope: {
|
||||||
event_tracking => ({
|
eventTracking: '@',
|
||||||
scope: {
|
eventSegmentation: '=?'
|
||||||
eventTracking: '@',
|
},
|
||||||
eventSegmentation: '=?'
|
link(scope, element, attrs) {
|
||||||
},
|
const sendGA = attrs.eventTrackingGa || false
|
||||||
link(scope, element, attrs) {
|
const sendMB = attrs.eventTrackingMb || false
|
||||||
const sendGA = attrs.eventTrackingGa || false
|
const sendMBFunction = attrs.eventTrackingSendOnce
|
||||||
const sendMB = attrs.eventTrackingMb || false
|
? 'sendMBOnce'
|
||||||
const sendMBFunction = attrs.eventTrackingSendOnce
|
: 'sendMB'
|
||||||
? 'sendMBOnce'
|
const sendGAFunction = attrs.eventTrackingSendOnce ? 'sendGAOnce' : 'send'
|
||||||
: 'sendMB'
|
const segmentation = scope.eventSegmentation || {}
|
||||||
const sendGAFunction = attrs.eventTrackingSendOnce
|
segmentation.page = window.location.pathname
|
||||||
? '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
|
@param {boolean} scrollEvent Use to unbind scroll event
|
||||||
*/
|
*/
|
||||||
if (sendMB) {
|
if (sendMB) {
|
||||||
event_tracking[sendMBFunction](scope.eventTracking, segmentation)
|
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 (sendGA) {
|
||||||
if (attrs.eventTrackingTrigger === 'load') {
|
event_tracking[sendGAFunction](
|
||||||
return sendEvent()
|
attrs.eventTrackingGa,
|
||||||
} else if (attrs.eventTrackingTrigger === 'click') {
|
attrs.eventTrackingAction || scope.eventTracking,
|
||||||
return element.on('click', e => sendEvent())
|
attrs.eventTrackingLabel || ''
|
||||||
} else if (attrs.eventTrackingTrigger === 'hover') {
|
)
|
||||||
let timer = null
|
}
|
||||||
let timeoutAmt = 500
|
if (scrollEvent) {
|
||||||
if (attrs.eventHoverAmt) {
|
return $(window).unbind('resize scroll')
|
||||||
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 (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) =>
|
define(['base', 'moment'], (App, moment) =>
|
||||||
App.controller('BinaryFileController', [
|
App.controller('BinaryFileController', function(
|
||||||
'$scope',
|
$scope,
|
||||||
'$rootScope',
|
$rootScope,
|
||||||
'$http',
|
$http,
|
||||||
'$timeout',
|
$timeout,
|
||||||
'$element',
|
$element,
|
||||||
'ide',
|
ide,
|
||||||
'waitFor',
|
waitFor
|
||||||
function($scope, $rootScope, $http, $timeout, $element, ide, waitFor) {
|
) {
|
||||||
const MAX_FILE_SIZE = 2 * 1024 * 1024
|
const MAX_FILE_SIZE = 2 * 1024 * 1024
|
||||||
const MAX_URL_LENGTH = 60
|
const MAX_URL_LENGTH = 60
|
||||||
const FRONT_OF_URL_LENGTH = 35
|
const FRONT_OF_URL_LENGTH = 35
|
||||||
const FILLER = '...'
|
const FILLER = '...'
|
||||||
const TAIL_OF_URL_LENGTH =
|
const TAIL_OF_URL_LENGTH =
|
||||||
MAX_URL_LENGTH - FRONT_OF_URL_LENGTH - FILLER.length
|
MAX_URL_LENGTH - FRONT_OF_URL_LENGTH - FILLER.length
|
||||||
|
|
||||||
const textExtensions = ['bib', 'tex', 'txt', 'cls', 'sty']
|
const textExtensions = ['bib', 'tex', 'txt', 'cls', 'sty']
|
||||||
const imageExtensions = ['png', 'jpg', 'jpeg', 'gif']
|
const imageExtensions = ['png', 'jpg', 'jpeg', 'gif']
|
||||||
const previewableExtensions = []
|
const previewableExtensions = []
|
||||||
|
|
||||||
const extension = file =>
|
const extension = file =>
|
||||||
file.name
|
file.name
|
||||||
.split('.')
|
.split('.')
|
||||||
.pop()
|
.pop()
|
||||||
.toLowerCase()
|
.toLowerCase()
|
||||||
|
|
||||||
$scope.isTextFile = () =>
|
$scope.isTextFile = () =>
|
||||||
textExtensions.indexOf(extension($scope.openFile)) > -1
|
textExtensions.indexOf(extension($scope.openFile)) > -1
|
||||||
$scope.isImageFile = () =>
|
$scope.isImageFile = () =>
|
||||||
imageExtensions.indexOf(extension($scope.openFile)) > -1
|
imageExtensions.indexOf(extension($scope.openFile)) > -1
|
||||||
$scope.isPreviewableFile = () =>
|
$scope.isPreviewableFile = () =>
|
||||||
previewableExtensions.indexOf(extension($scope.openFile)) > -1
|
previewableExtensions.indexOf(extension($scope.openFile)) > -1
|
||||||
$scope.isUnpreviewableFile = () =>
|
$scope.isUnpreviewableFile = () =>
|
||||||
!$scope.isTextFile() &&
|
!$scope.isTextFile() &&
|
||||||
!$scope.isImageFile() &&
|
!$scope.isImageFile() &&
|
||||||
!$scope.isPreviewableFile()
|
!$scope.isPreviewableFile()
|
||||||
|
|
||||||
$scope.textPreview = {
|
$scope.textPreview = {
|
||||||
loading: false,
|
loading: false,
|
||||||
shouldShowDots: false,
|
shouldShowDots: false,
|
||||||
error: false,
|
error: false,
|
||||||
data: null
|
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.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
|
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
|
||||||
*/
|
*/
|
||||||
define(['base', 'ide/colors/ColorManager'], (App, ColorManager) =>
|
define(['base', 'ide/colors/ColorManager'], (App, ColorManager) =>
|
||||||
App.controller('ChatMessageController', [
|
App.controller('ChatMessageController', function($scope, ide) {
|
||||||
'$scope',
|
const hslColorConfigs = {
|
||||||
'ide',
|
borderSaturation:
|
||||||
function($scope, ide) {
|
(window.uiConfig != null
|
||||||
const hslColorConfigs = {
|
? window.uiConfig.chatMessageBorderSaturation
|
||||||
borderSaturation:
|
: undefined) || '70%',
|
||||||
(window.uiConfig != null
|
borderLightness:
|
||||||
? window.uiConfig.chatMessageBorderSaturation
|
(window.uiConfig != null
|
||||||
: undefined) || '70%',
|
? window.uiConfig.chatMessageBorderLightness
|
||||||
borderLightness:
|
: undefined) || '70%',
|
||||||
(window.uiConfig != null
|
bgSaturation:
|
||||||
? window.uiConfig.chatMessageBorderLightness
|
(window.uiConfig != null
|
||||||
: undefined) || '70%',
|
? window.uiConfig.chatMessageBgSaturation
|
||||||
bgSaturation:
|
: undefined) || '60%',
|
||||||
(window.uiConfig != null
|
bgLightness:
|
||||||
? window.uiConfig.chatMessageBgSaturation
|
(window.uiConfig != null
|
||||||
: undefined) || '60%',
|
? window.uiConfig.chatMessageBgLightness
|
||||||
bgLightness:
|
: undefined) || '97%'
|
||||||
(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})`
|
|
||||||
}))
|
|
||||||
}
|
}
|
||||||
]))
|
|
||||||
|
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
|
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
|
||||||
*/
|
*/
|
||||||
define(['base', 'libs/jquery-layout', 'libs/jquery.ui.touch-punch'], App =>
|
define(['base', 'libs/jquery-layout', 'libs/jquery.ui.touch-punch'], App =>
|
||||||
App.directive('layout', [
|
App.directive('layout', ($parse, $compile, ide) => ({
|
||||||
'$parse',
|
compile() {
|
||||||
'$compile',
|
return {
|
||||||
'ide',
|
pre(scope, element, attrs) {
|
||||||
($parse, $compile, ide) => ({
|
let customTogglerEl, spacingClosed, spacingOpen, state
|
||||||
compile() {
|
const name = attrs.layout
|
||||||
return {
|
|
||||||
pre(scope, element, attrs) {
|
|
||||||
let customTogglerEl, spacingClosed, spacingOpen, state
|
|
||||||
const name = attrs.layout
|
|
||||||
|
|
||||||
const { customTogglerPane } = attrs
|
const { customTogglerPane } = attrs
|
||||||
const { customTogglerMsgWhenOpen } = attrs
|
const { customTogglerMsgWhenOpen } = attrs
|
||||||
const { customTogglerMsgWhenClosed } = attrs
|
const { customTogglerMsgWhenClosed } = attrs
|
||||||
const hasCustomToggler =
|
const hasCustomToggler =
|
||||||
customTogglerPane != null &&
|
customTogglerPane != null &&
|
||||||
customTogglerMsgWhenOpen != null &&
|
customTogglerMsgWhenOpen != null &&
|
||||||
customTogglerMsgWhenClosed != null
|
customTogglerMsgWhenClosed != null
|
||||||
|
|
||||||
if (attrs.spacingOpen != null) {
|
if (attrs.spacingOpen != null) {
|
||||||
spacingOpen = parseInt(attrs.spacingOpen, 10)
|
spacingOpen = parseInt(attrs.spacingOpen, 10)
|
||||||
} else {
|
} else {
|
||||||
spacingOpen = window.uiConfig.defaultResizerSizeOpen
|
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) {
|
// Restore previously recorded state
|
||||||
spacingClosed = parseInt(attrs.spacingClosed, 10)
|
if ((state = ide.localStorage(`layout.${name}`)) != null) {
|
||||||
} else {
|
if (state.east != null) {
|
||||||
spacingClosed = window.uiConfig.defaultResizerSizeClosed
|
if (
|
||||||
}
|
attrs.minimumRestoreSizeEast == null ||
|
||||||
|
(state.east.size >= attrs.minimumRestoreSizeEast &&
|
||||||
const options = {
|
!state.east.initClosed)
|
||||||
spacing_open: spacingOpen,
|
) {
|
||||||
spacing_closed: spacingClosed,
|
options.east = state.east
|
||||||
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 (state.west != null) {
|
||||||
// Restore previously recorded state
|
if (
|
||||||
if ((state = ide.localStorage(`layout.${name}`)) != null) {
|
attrs.minimumRestoreSizeWest == null ||
|
||||||
if (state.east != null) {
|
(state.west.size >= attrs.minimumRestoreSizeWest &&
|
||||||
if (
|
!state.west.initClosed)
|
||||||
attrs.minimumRestoreSizeEast == null ||
|
) {
|
||||||
(state.east.size >= attrs.minimumRestoreSizeEast &&
|
options.west = state.west
|
||||||
!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 (window.uiConfig.eastResizerCursor != null) {
|
if (window.uiConfig.eastResizerCursor != null) {
|
||||||
options.east.resizerCursor = window.uiConfig.eastResizerCursor
|
options.east.resizerCursor = window.uiConfig.eastResizerCursor
|
||||||
}
|
}
|
||||||
|
|
||||||
if (window.uiConfig.westResizerCursor != null) {
|
if (window.uiConfig.westResizerCursor != null) {
|
||||||
options.west.resizerCursor = window.uiConfig.westResizerCursor
|
options.west.resizerCursor = window.uiConfig.westResizerCursor
|
||||||
}
|
}
|
||||||
|
|
||||||
const repositionControls = function() {
|
const repositionControls = function() {
|
||||||
state = element.layout().readState()
|
state = element.layout().readState()
|
||||||
if (state.east != null) {
|
if (state.east != null) {
|
||||||
const controls = element.find('> .ui-layout-resizer-controls')
|
const controls = element.find('> .ui-layout-resizer-controls')
|
||||||
if (state.east.initClosed) {
|
if (state.east.initClosed) {
|
||||||
return controls.hide()
|
return controls.hide()
|
||||||
} else {
|
} else {
|
||||||
controls.show()
|
controls.show()
|
||||||
return controls.css({
|
return controls.css({
|
||||||
right: state.east.size
|
right: state.east.size
|
||||||
})
|
})
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const repositionCustomToggler = function() {
|
const repositionCustomToggler = function() {
|
||||||
if (customTogglerEl == null) {
|
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
|
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() {
|
return element.layout().resizeAll()
|
||||||
state = element.layout().readState()
|
}
|
||||||
if (attrs.openEast != null && state.east != null) {
|
|
||||||
const openEast = $parse(attrs.openEast)
|
element.layout(options)
|
||||||
return openEast.assign(scope, !state.east.initClosed)
|
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
|
customTogglerScope.tooltipMsgWhenOpen = customTogglerMsgWhenOpen
|
||||||
var onInternalResize = function() {
|
customTogglerScope.tooltipMsgWhenClosed = customTogglerMsgWhenClosed
|
||||||
state = element.layout().readState()
|
|
||||||
scope.$broadcast(`layout:${name}:resize`, state)
|
customTogglerScope.tooltipPlacement =
|
||||||
repositionControls()
|
customTogglerPane === 'east' ? 'left' : 'right'
|
||||||
if (hasCustomToggler) {
|
customTogglerScope.handleClick = function() {
|
||||||
repositionCustomToggler()
|
element.layout().toggle(customTogglerPane)
|
||||||
}
|
return repositionCustomToggler()
|
||||||
return resetOpenStates()
|
|
||||||
}
|
}
|
||||||
|
customTogglerEl = $compile(`\
|
||||||
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(`\
|
|
||||||
<a href \
|
<a href \
|
||||||
ng-show=\"isVisible\" \
|
ng-show=\"isVisible\" \
|
||||||
class=\"custom-toggler ${`custom-toggler-${customTogglerPane}`}\" \
|
class=\"custom-toggler ${`custom-toggler-${customTogglerPane}`}\" \
|
||||||
|
@ -214,82 +210,81 @@ tooltip=\"{{ isOpen ? tooltipMsgWhenOpen : tooltipMsgWhenClosed }}\" \
|
||||||
tooltip-placement=\"{{ tooltipPlacement }}\" \
|
tooltip-placement=\"{{ tooltipPlacement }}\" \
|
||||||
ng-click=\"handleClick()\">\
|
ng-click=\"handleClick()\">\
|
||||||
`)(customTogglerScope)
|
`)(customTogglerScope)
|
||||||
element.append(customTogglerEl)
|
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)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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
|
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
|
||||||
*/
|
*/
|
||||||
define(['base', 'ide/editor/Document'], (App, Document) =>
|
define(['base', 'ide/editor/Document'], (App, Document) =>
|
||||||
App.controller('SavingNotificationController', [
|
App.controller('SavingNotificationController', function(
|
||||||
'$scope',
|
$scope,
|
||||||
'$interval',
|
$interval,
|
||||||
'ide',
|
ide
|
||||||
function($scope, $interval, ide) {
|
) {
|
||||||
let warnAboutUnsavedChanges
|
let warnAboutUnsavedChanges
|
||||||
setInterval(() => pollSavedStatus(), 1000)
|
setInterval(() => pollSavedStatus(), 1000)
|
||||||
|
|
||||||
$(window).bind('beforeunload', () => {
|
$(window).bind('beforeunload', () => {
|
||||||
return warnAboutUnsavedChanges()
|
return warnAboutUnsavedChanges()
|
||||||
})
|
})
|
||||||
|
|
||||||
let lockEditorModal = null // modal showing "connection lost"
|
let lockEditorModal = null // modal showing "connection lost"
|
||||||
const MAX_UNSAVED_SECONDS = 15 // lock the editor after this time if unsaved
|
const MAX_UNSAVED_SECONDS = 15 // lock the editor after this time if unsaved
|
||||||
|
|
||||||
$scope.docSavingStatus = {}
|
$scope.docSavingStatus = {}
|
||||||
var pollSavedStatus = function() {
|
var pollSavedStatus = function() {
|
||||||
let t
|
let t
|
||||||
const oldStatus = $scope.docSavingStatus
|
const oldStatus = $scope.docSavingStatus
|
||||||
const oldUnsavedCount = $scope.docSavingStatusCount
|
const oldUnsavedCount = $scope.docSavingStatusCount
|
||||||
const newStatus = {}
|
const newStatus = {}
|
||||||
let newUnsavedCount = 0
|
let newUnsavedCount = 0
|
||||||
let maxUnsavedSeconds = 0
|
let maxUnsavedSeconds = 0
|
||||||
|
|
||||||
for (let doc_id in Document.openDocs) {
|
for (let doc_id in Document.openDocs) {
|
||||||
const doc = Document.openDocs[doc_id]
|
const doc = Document.openDocs[doc_id]
|
||||||
const saving = doc.pollSavedStatus()
|
const saving = doc.pollSavedStatus()
|
||||||
if (!saving) {
|
if (!saving) {
|
||||||
newUnsavedCount++
|
newUnsavedCount++
|
||||||
if (oldStatus[doc_id] != null) {
|
if (oldStatus[doc_id] != null) {
|
||||||
newStatus[doc_id] = oldStatus[doc_id]
|
newStatus[doc_id] = oldStatus[doc_id]
|
||||||
t = newStatus[doc_id].unsavedSeconds += 1
|
t = newStatus[doc_id].unsavedSeconds += 1
|
||||||
if (t > maxUnsavedSeconds) {
|
if (t > maxUnsavedSeconds) {
|
||||||
maxUnsavedSeconds = t
|
maxUnsavedSeconds = t
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
newStatus[doc_id] = {
|
newStatus[doc_id] = {
|
||||||
unsavedSeconds: 0,
|
unsavedSeconds: 0,
|
||||||
doc: ide.fileTreeManager.findEntityById(doc_id)
|
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 (newUnsavedCount > 0 && t > MAX_UNSAVED_SECONDS && !lockEditorModal) {
|
||||||
if (Document.hasUnsavedChanges()) {
|
lockEditorModal = ide.showLockEditorMessageModal(
|
||||||
return 'You have unsaved changes. If you leave now they will not be saved.'
|
'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,
|
App,
|
||||||
iconTypeFromName
|
iconTypeFromName
|
||||||
) {
|
) {
|
||||||
App.controller('FileTreeEntityController', [
|
App.controller('FileTreeEntityController', function($scope, ide, $modal) {
|
||||||
'$scope',
|
$scope.select = function(e) {
|
||||||
'ide',
|
if (e.ctrlKey || e.metaKey) {
|
||||||
'$modal',
|
e.stopPropagation()
|
||||||
function($scope, ide, $modal) {
|
const initialMultiSelectCount = ide.fileTreeManager.multiSelectedCount()
|
||||||
$scope.select = function(e) {
|
ide.fileTreeManager.toggleMultiSelectEntity($scope.entity) === 0
|
||||||
if (e.ctrlKey || e.metaKey) {
|
if (initialMultiSelectCount === 0) {
|
||||||
e.stopPropagation()
|
// On first multi selection, also include the current active/open file.
|
||||||
const initialMultiSelectCount = ide.fileTreeManager.multiSelectedCount()
|
return ide.fileTreeManager.multiSelectSelectedEntity()
|
||||||
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)
|
|
||||||
}
|
}
|
||||||
|
} 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() {
|
const { name } = $scope.inputs
|
||||||
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 }
|
// validator will set name to undefined for invalid filenames
|
||||||
|
if (name == null) {
|
||||||
$scope.startRenaming = () => ($scope.entity.renaming = true)
|
// Showing the modal blurs the rename box which calls us again
|
||||||
|
// so track this with the invalidModalShowing flag
|
||||||
let invalidModalShowing = false
|
if (invalidModalShowing) {
|
||||||
$scope.finishRenaming = function() {
|
|
||||||
// avoid double events when blur and on-enter fire together
|
|
||||||
if (!$scope.entity.renaming) {
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
invalidModalShowing = true
|
||||||
const { name } = $scope.inputs
|
const modal = $modal.open({
|
||||||
|
templateUrl: 'invalidFileNameModalTemplate'
|
||||||
// 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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
|
modal.result.then(() => (invalidModalShowing = false))
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
$scope.$on('delete:selected', function() {
|
delete $scope.entity.renaming
|
||||||
if ($scope.entity.selected) {
|
if (name == null || name.length === 0) {
|
||||||
return $scope.openDeleteModal()
|
$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.$on('delete:selected', function() {
|
||||||
'$scope',
|
if ($scope.entity.selected) {
|
||||||
'ide',
|
return $scope.openDeleteModal()
|
||||||
'$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()
|
|
||||||
}
|
}
|
||||||
|
})
|
||||||
|
|
||||||
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
|
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
|
||||||
*/
|
*/
|
||||||
define(['base'], App =>
|
define(['base'], App =>
|
||||||
App.controller('FileTreeRootFolderController', [
|
App.controller('FileTreeRootFolderController', function($scope, ide) {
|
||||||
'$scope',
|
const { rootFolder } = $scope
|
||||||
'ide',
|
return ($scope.onDrop = function(events, ui) {
|
||||||
function($scope, ide) {
|
let entities
|
||||||
const { rootFolder } = $scope
|
if (ide.fileTreeManager.multiSelectedCount()) {
|
||||||
return ($scope.onDrop = function(events, ui) {
|
entities = ide.fileTreeManager.getMultiSelectedEntityChildNodes()
|
||||||
let entities
|
} else {
|
||||||
if (ide.fileTreeManager.multiSelectedCount()) {
|
entities = [$(ui.draggable).scope().entity]
|
||||||
entities = ide.fileTreeManager.getMultiSelectedEntityChildNodes()
|
}
|
||||||
} else {
|
for (let dropped_entity of Array.from(entities)) {
|
||||||
entities = [$(ui.draggable).scope().entity]
|
ide.fileTreeManager.moveEntity(dropped_entity, rootFolder)
|
||||||
}
|
}
|
||||||
for (let dropped_entity of Array.from(entities)) {
|
$scope.$digest()
|
||||||
ide.fileTreeManager.moveEntity(dropped_entity, rootFolder)
|
// clear highlight explicitly
|
||||||
}
|
return $('.file-tree-inner .droppable-hover').removeClass(
|
||||||
$scope.$digest()
|
'droppable-hover'
|
||||||
// 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
|
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
|
||||||
*/
|
*/
|
||||||
define(['base'], App =>
|
define(['base'], App =>
|
||||||
App.directive('fileEntity', [
|
App.directive('fileEntity', RecursionHelper => ({
|
||||||
'RecursionHelper',
|
restrict: 'E',
|
||||||
RecursionHelper => ({
|
scope: {
|
||||||
restrict: 'E',
|
entity: '=',
|
||||||
scope: {
|
permissions: '='
|
||||||
entity: '=',
|
},
|
||||||
permissions: '='
|
templateUrl: 'entityListItemTemplate',
|
||||||
},
|
compile(element) {
|
||||||
templateUrl: 'entityListItemTemplate',
|
return RecursionHelper.compile(element, function(
|
||||||
compile(element) {
|
scope,
|
||||||
return RecursionHelper.compile(element, function(
|
element,
|
||||||
scope,
|
attrs,
|
||||||
element,
|
ctrl
|
||||||
attrs,
|
) {
|
||||||
ctrl
|
// Don't freak out if we're already in an apply callback
|
||||||
) {
|
scope.$originalApply = scope.$apply
|
||||||
// Don't freak out if we're already in an apply callback
|
return (scope.$apply = function(fn) {
|
||||||
scope.$originalApply = scope.$apply
|
if (fn == null) {
|
||||||
return (scope.$apply = function(fn) {
|
fn = function() {}
|
||||||
if (fn == null) {
|
}
|
||||||
fn = function() {}
|
const phase = this.$root.$$phase
|
||||||
}
|
if (phase === '$apply' || phase === '$digest') {
|
||||||
const phase = this.$root.$$phase
|
return fn()
|
||||||
if (phase === '$apply' || phase === '$digest') {
|
} else {
|
||||||
return fn()
|
return this.$originalApply(fn)
|
||||||
} else {
|
}
|
||||||
return this.$originalApply(fn)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
}
|
})
|
||||||
})
|
}
|
||||||
]))
|
})))
|
||||||
|
|
|
@ -18,73 +18,118 @@ define(['base', 'ide/history/util/displayNameForUser'], function(
|
||||||
App,
|
App,
|
||||||
displayNameForUser
|
displayNameForUser
|
||||||
) {
|
) {
|
||||||
App.controller('HistoryListController', [
|
App.controller('HistoryListController', function($scope, $modal, ide) {
|
||||||
'$scope',
|
$scope.hoveringOverListSelectors = false
|
||||||
'$modal',
|
|
||||||
'ide',
|
|
||||||
function($scope, $modal, ide) {
|
|
||||||
$scope.hoveringOverListSelectors = false
|
|
||||||
|
|
||||||
$scope.projectUsers = []
|
$scope.projectUsers = []
|
||||||
|
|
||||||
$scope.$watch('project.members', function(newVal) {
|
$scope.$watch('project.members', function(newVal) {
|
||||||
if (newVal != null) {
|
if (newVal != null) {
|
||||||
return ($scope.projectUsers = newVal.concat($scope.project.owner))
|
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
|
$scope.loadMore = () => {
|
||||||
// injected into the history API responses, so we won't need to fetch user data from other
|
return ide.historyManager.fetchNextBatchOfUpdates()
|
||||||
// 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.recalculateSelectedUpdates = function() {
|
||||||
|
let beforeSelection = true
|
||||||
$scope.deleteLabel = labelDetails =>
|
let afterSelection = false
|
||||||
$modal.open({
|
$scope.history.selection.updates = []
|
||||||
templateUrl: 'historyV2DeleteLabelModalTemplate',
|
return (() => {
|
||||||
controller: 'HistoryV2DeleteLabelModalController',
|
const result = []
|
||||||
resolve: {
|
for (let update of Array.from($scope.history.updates)) {
|
||||||
labelDetails() {
|
var inSelection
|
||||||
return labelDetails
|
if (update.selectedTo) {
|
||||||
}
|
inSelection = true
|
||||||
|
beforeSelection = false
|
||||||
}
|
}
|
||||||
})
|
|
||||||
|
|
||||||
$scope.loadMore = () => {
|
update.beforeSelection = beforeSelection
|
||||||
return ide.historyManager.fetchNextBatchOfUpdates()
|
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() {
|
if (hoverSelectedFrom) {
|
||||||
let beforeSelection = true
|
// We want to 'hover select' everything between hoverSelectedFrom and selectedTo
|
||||||
let afterSelection = false
|
inHoverSelection = false
|
||||||
$scope.history.selection.updates = []
|
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 (() => {
|
return (() => {
|
||||||
const result = []
|
const result = []
|
||||||
for (let update of Array.from($scope.history.updates)) {
|
for (update of Array.from($scope.history.updates)) {
|
||||||
var inSelection
|
if (update.hoverSelectedTo) {
|
||||||
if (update.selectedTo) {
|
inHoverSelection = true
|
||||||
inSelection = true
|
|
||||||
beforeSelection = false
|
|
||||||
}
|
}
|
||||||
|
update.inHoverSelection = inHoverSelection
|
||||||
update.beforeSelection = beforeSelection
|
|
||||||
update.inSelection = inSelection
|
|
||||||
update.afterSelection = afterSelection
|
|
||||||
|
|
||||||
if (inSelection) {
|
|
||||||
$scope.history.selection.updates.push(update)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (update.selectedFrom) {
|
if (update.selectedFrom) {
|
||||||
inSelection = false
|
update.hoverSelectedFrom = true
|
||||||
result.push((afterSelection = true))
|
result.push((inHoverSelection = false))
|
||||||
} else {
|
} else {
|
||||||
result.push(undefined)
|
result.push(undefined)
|
||||||
}
|
}
|
||||||
|
@ -92,132 +137,81 @@ define(['base', 'ide/history/util/displayNameForUser'], function(
|
||||||
return result
|
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.resetHoverState = () =>
|
||||||
'$scope',
|
(() => {
|
||||||
'event_tracking',
|
const result = []
|
||||||
function($scope, event_tracking) {
|
for (let update of Array.from($scope.history.updates)) {
|
||||||
$scope.$watch('update.selectedFrom', function(
|
delete update.hoverSelectedFrom
|
||||||
selectedFrom,
|
delete update.hoverSelectedTo
|
||||||
oldSelectedFrom
|
result.push(delete update.inHoverSelection)
|
||||||
) {
|
|
||||||
if (selectedFrom) {
|
|
||||||
for (let update of Array.from($scope.history.updates)) {
|
|
||||||
if (update !== $scope.update) {
|
|
||||||
update.selectedFrom = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return $scope.recalculateSelectedUpdates()
|
|
||||||
}
|
}
|
||||||
})
|
return result
|
||||||
|
})()
|
||||||
|
|
||||||
$scope.$watch('update.selectedTo', function(selectedTo, oldSelectedTo) {
|
return $scope.$watch('history.updates.length', () =>
|
||||||
if (selectedTo) {
|
$scope.recalculateSelectedUpdates()
|
||||||
for (let update of Array.from($scope.history.updates)) {
|
)
|
||||||
if (update !== $scope.update) {
|
})
|
||||||
update.selectedTo = false
|
|
||||||
}
|
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()
|
|
||||||
}
|
}
|
||||||
})
|
return $scope.recalculateSelectedUpdates()
|
||||||
|
|
||||||
$scope.select = function() {
|
|
||||||
event_tracking.sendMB('history-view-change')
|
|
||||||
$scope.update.selectedTo = true
|
|
||||||
return ($scope.update.selectedFrom = true)
|
|
||||||
}
|
}
|
||||||
|
})
|
||||||
|
|
||||||
$scope.mouseOverSelectedFrom = function() {
|
$scope.$watch('update.selectedTo', function(selectedTo, oldSelectedTo) {
|
||||||
$scope.history.hoveringOverListSelectors = true
|
if (selectedTo) {
|
||||||
$scope.update.hoverSelectedFrom = true
|
for (let update of Array.from($scope.history.updates)) {
|
||||||
return $scope.recalculateHoveredUpdates()
|
if (update !== $scope.update) {
|
||||||
|
update.selectedTo = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return $scope.recalculateSelectedUpdates()
|
||||||
}
|
}
|
||||||
|
})
|
||||||
|
|
||||||
$scope.mouseOutSelectedFrom = function() {
|
$scope.select = function() {
|
||||||
$scope.history.hoveringOverListSelectors = false
|
event_tracking.sendMB('history-view-change')
|
||||||
return $scope.resetHoverState()
|
$scope.update.selectedTo = true
|
||||||
}
|
return ($scope.update.selectedFrom = true)
|
||||||
|
|
||||||
$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.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
|
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
|
||||||
*/
|
*/
|
||||||
define(['base'], App =>
|
define(['base'], App =>
|
||||||
App.controller('HistoryV2AddLabelModalController', [
|
App.controller('HistoryV2AddLabelModalController', function(
|
||||||
'$scope',
|
$scope,
|
||||||
'$modalInstance',
|
$modalInstance,
|
||||||
'ide',
|
ide,
|
||||||
'update',
|
update
|
||||||
function($scope, $modalInstance, ide, update) {
|
) {
|
||||||
$scope.update = update
|
$scope.update = update
|
||||||
$scope.inputs = { labelName: null }
|
$scope.inputs = { labelName: null }
|
||||||
$scope.state = {
|
$scope.state = {
|
||||||
inflight: false,
|
inflight: false,
|
||||||
error: 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)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
]))
|
|
||||||
|
$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
|
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
|
||||||
*/
|
*/
|
||||||
define(['base'], App =>
|
define(['base'], App =>
|
||||||
App.controller('HistoryV2DeleteLabelModalController', [
|
App.controller('HistoryV2DeleteLabelModalController', function(
|
||||||
'$scope',
|
$scope,
|
||||||
'$modalInstance',
|
$modalInstance,
|
||||||
'ide',
|
ide,
|
||||||
'labelDetails',
|
labelDetails
|
||||||
function($scope, $modalInstance, ide, labelDetails) {
|
) {
|
||||||
$scope.labelDetails = labelDetails
|
$scope.labelDetails = labelDetails
|
||||||
$scope.state = {
|
$scope.state = {
|
||||||
inflight: false,
|
inflight: false,
|
||||||
error: 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)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
]))
|
|
||||||
|
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
|
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
|
||||||
*/
|
*/
|
||||||
define(['base'], App =>
|
define(['base'], App =>
|
||||||
App.controller('HistoryV2FileTreeController', [
|
App.controller('HistoryV2FileTreeController', function($scope, ide) {
|
||||||
'$scope',
|
$scope.handleFileSelection = file => {
|
||||||
'ide',
|
ide.historyManager.selectFile(file)
|
||||||
function($scope, ide) {
|
|
||||||
$scope.handleFileSelection = file => {
|
|
||||||
ide.historyManager.selectFile(file)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
]))
|
}))
|
||||||
|
|
|
@ -15,48 +15,40 @@ define(['base', 'ide/history/util/displayNameForUser'], (
|
||||||
App,
|
App,
|
||||||
displayNameForUser
|
displayNameForUser
|
||||||
) =>
|
) =>
|
||||||
App.controller('HistoryV2ListController', [
|
App.controller('HistoryV2ListController', function($scope, $modal, ide) {
|
||||||
'$scope',
|
$scope.hoveringOverListSelectors = false
|
||||||
'$modal',
|
$scope.listConfig = { showOnlyLabelled: false }
|
||||||
'ide',
|
|
||||||
function($scope, $modal, ide) {
|
|
||||||
$scope.hoveringOverListSelectors = false
|
|
||||||
$scope.listConfig = { showOnlyLabelled: false }
|
|
||||||
|
|
||||||
$scope.projectUsers = []
|
$scope.projectUsers = []
|
||||||
|
|
||||||
$scope.$watch('project.members', function(newVal) {
|
$scope.$watch('project.members', function(newVal) {
|
||||||
if (newVal != null) {
|
if (newVal != null) {
|
||||||
return ($scope.projectUsers = newVal.concat($scope.project.owner))
|
return ($scope.projectUsers = newVal.concat($scope.project.owner))
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
$scope.loadMore = () => {
|
|
||||||
return ide.historyManager.fetchNextBatchOfUpdates()
|
|
||||||
}
|
}
|
||||||
|
})
|
||||||
|
|
||||||
$scope.handleVersionSelect = version =>
|
$scope.loadMore = () => {
|
||||||
$scope.$applyAsync(() =>
|
return ide.historyManager.fetchNextBatchOfUpdates()
|
||||||
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.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
|
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
|
||||||
*/
|
*/
|
||||||
define(['base'], App =>
|
define(['base'], App =>
|
||||||
App.controller('HistoryV2ToolbarController', [
|
App.controller(
|
||||||
'$scope',
|
'HistoryV2ToolbarController',
|
||||||
'$modal',
|
|
||||||
'ide',
|
|
||||||
'event_tracking',
|
|
||||||
'waitFor',
|
|
||||||
($scope, $modal, ide, event_tracking, waitFor) => {
|
($scope, $modal, ide, event_tracking, waitFor) => {
|
||||||
let openEntity
|
let openEntity
|
||||||
|
|
||||||
|
@ -126,4 +122,4 @@ define(['base'], App =>
|
||||||
.catch(err => console.warn(err))
|
.catch(err => console.warn(err))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]))
|
))
|
||||||
|
|
|
@ -30,7 +30,7 @@ define([
|
||||||
// and then again on ack.
|
// and then again on ack.
|
||||||
const AUTO_COMPILE_DEBOUNCE = 2000
|
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(
|
App.controller('PdfController', function(
|
||||||
$scope,
|
$scope,
|
||||||
|
@ -898,188 +898,175 @@ define([
|
||||||
}))
|
}))
|
||||||
})
|
})
|
||||||
|
|
||||||
App.factory('synctex', [
|
App.factory('synctex', function(ide, $http, $q) {
|
||||||
'ide',
|
// enable per-user containers by default
|
||||||
'$http',
|
const perUserCompile = true
|
||||||
'$q',
|
|
||||||
function(ide, $http, $q) {
|
|
||||||
// enable per-user containers by default
|
|
||||||
const perUserCompile = true
|
|
||||||
|
|
||||||
const synctex = {
|
const synctex = {
|
||||||
syncToPdf(cursorPosition) {
|
syncToPdf(cursorPosition) {
|
||||||
const deferred = $q.defer()
|
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 doc_id = ide.editorManager.getCurrentDocId()
|
||||||
|
if (doc_id == null) {
|
||||||
|
deferred.reject()
|
||||||
return deferred.promise
|
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) {
|
// If the root file is folder/main.tex, then synctex sees the
|
||||||
let v
|
// path as folder/./main.tex
|
||||||
if (options == null) {
|
const rootDocDirname = ide.fileTreeManager.getRootDocDirname()
|
||||||
options = {}
|
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) {
|
.then(function(response) {
|
||||||
deferred.reject()
|
const { data } = response
|
||||||
return deferred.promise
|
return deferred.resolve(data.pdf || [])
|
||||||
}
|
|
||||||
|
|
||||||
// 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) {
|
.catch(function(response) {
|
||||||
const { data } = response
|
const error = response.data
|
||||||
if (
|
return deferred.reject(error)
|
||||||
data.code != null &&
|
})
|
||||||
data.code.length > 0 &&
|
|
||||||
data.code[0].file !== ''
|
return deferred.promise
|
||||||
) {
|
},
|
||||||
const doc = ide.fileTreeManager.findEntityByPath(
|
|
||||||
data.code[0].file
|
syncToCode(position, options) {
|
||||||
)
|
let v
|
||||||
if (doc == null) {
|
if (options == null) {
|
||||||
return
|
options = {}
|
||||||
}
|
}
|
||||||
return deferred.resolve({ doc, line: data.code[0].line })
|
const deferred = $q.defer()
|
||||||
} else if (data.code[0].file === '') {
|
if (position == null) {
|
||||||
ide.$scope.sync_tex_error = true
|
deferred.reject()
|
||||||
setTimeout(() => (ide.$scope.sync_tex_error = false), 4000)
|
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
|
||||||
}
|
}
|
||||||
})
|
return deferred.resolve({ doc, line: data.code[0].line })
|
||||||
.catch(function(response) {
|
} else if (data.code[0].file === '') {
|
||||||
const error = response.data
|
ide.$scope.sync_tex_error = true
|
||||||
return deferred.reject(error)
|
setTimeout(() => (ide.$scope.sync_tex_error = false), 4000)
|
||||||
})
|
}
|
||||||
|
})
|
||||||
return deferred.promise
|
.catch(function(response) {
|
||||||
}
|
const error = response.data
|
||||||
}
|
return deferred.reject(error)
|
||||||
|
|
||||||
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
|
|
||||||
})
|
})
|
||||||
.then(function(data) {
|
|
||||||
const { doc, line } = data
|
|
||||||
return ide.editorManager.openDoc(doc, { gotoLine: line })
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
])
|
|
||||||
|
|
||||||
App.controller('PdfLogEntryController', [
|
return deferred.promise
|
||||||
'$scope',
|
}
|
||||||
'ide',
|
}
|
||||||
'event_tracking',
|
|
||||||
|
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, ide, event_tracking) =>
|
||||||
($scope.openInEditor = function(entry) {
|
($scope.openInEditor = function(entry) {
|
||||||
let column, line
|
let column, line
|
||||||
|
@ -1099,25 +1086,24 @@ define([
|
||||||
gotoColumn: column
|
gotoColumn: column
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
])
|
)
|
||||||
|
|
||||||
return App.controller('ClearCacheModalController', [
|
return App.controller('ClearCacheModalController', function(
|
||||||
'$scope',
|
$scope,
|
||||||
'$modalInstance',
|
$modalInstance
|
||||||
function($scope, $modalInstance) {
|
) {
|
||||||
$scope.state = { inflight: false }
|
$scope.state = { inflight: false }
|
||||||
|
|
||||||
$scope.clear = function() {
|
$scope.clear = function() {
|
||||||
$scope.state.inflight = true
|
$scope.state.inflight = true
|
||||||
return $scope.clearCache().then(function() {
|
return $scope.clearCache().then(function() {
|
||||||
$scope.state.inflight = false
|
$scope.state.inflight = false
|
||||||
return $modalInstance.close()
|
return $modalInstance.close()
|
||||||
})
|
})
|
||||||
}
|
|
||||||
|
|
||||||
return ($scope.cancel = () => $modalInstance.dismiss('cancel'))
|
|
||||||
}
|
}
|
||||||
])
|
|
||||||
|
return ($scope.cancel = () => $modalInstance.dismiss('cancel'))
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
function __guard__(value, transform) {
|
function __guard__(value, transform) {
|
||||||
|
|
|
@ -16,41 +16,39 @@
|
||||||
define(['base', 'pdfjs-dist/build/pdf'], (App, PDFJS) =>
|
define(['base', 'pdfjs-dist/build/pdf'], (App, PDFJS) =>
|
||||||
// app = angular.module 'pdfHighlights', []
|
// app = angular.module 'pdfHighlights', []
|
||||||
|
|
||||||
App.factory('pdfHighlights', [
|
App.factory('pdfHighlights', function() {
|
||||||
function() {
|
let pdfHighlights
|
||||||
let pdfHighlights
|
return (pdfHighlights = class pdfHighlights {
|
||||||
return (pdfHighlights = class pdfHighlights {
|
constructor(options) {
|
||||||
constructor(options) {
|
this.highlightsLayerDiv = options.highlights[0]
|
||||||
this.highlightsLayerDiv = options.highlights[0]
|
this.highlightElements = []
|
||||||
this.highlightElements = []
|
}
|
||||||
}
|
|
||||||
|
|
||||||
addHighlight(viewport, left, top, width, height) {
|
addHighlight(viewport, left, top, width, height) {
|
||||||
let rect = viewport.convertToViewportRectangle([
|
let rect = viewport.convertToViewportRectangle([
|
||||||
left,
|
left,
|
||||||
top,
|
top,
|
||||||
left + width,
|
left + width,
|
||||||
top + height
|
top + height
|
||||||
])
|
])
|
||||||
rect = PDFJS.Util.normalizeRect(rect)
|
rect = PDFJS.Util.normalizeRect(rect)
|
||||||
const element = document.createElement('div')
|
const element = document.createElement('div')
|
||||||
element.style.left = Math.floor(rect[0]) + 'px'
|
element.style.left = Math.floor(rect[0]) + 'px'
|
||||||
element.style.top = Math.floor(rect[1]) + 'px'
|
element.style.top = Math.floor(rect[1]) + 'px'
|
||||||
element.style.width = Math.ceil(rect[2] - rect[0]) + 'px'
|
element.style.width = Math.ceil(rect[2] - rect[0]) + 'px'
|
||||||
element.style.height = Math.ceil(rect[3] - rect[1]) + 'px'
|
element.style.height = Math.ceil(rect[3] - rect[1]) + 'px'
|
||||||
this.highlightElements.push(element)
|
this.highlightElements.push(element)
|
||||||
this.highlightsLayerDiv.appendChild(element)
|
this.highlightsLayerDiv.appendChild(element)
|
||||||
return element
|
return element
|
||||||
}
|
}
|
||||||
|
|
||||||
clearHighlights() {
|
clearHighlights() {
|
||||||
for (let h of Array.from(this.highlightElements)) {
|
for (let h of Array.from(this.highlightElements)) {
|
||||||
if (h != null) {
|
if (h != null) {
|
||||||
h.parentNode.removeChild(h)
|
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
|
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
|
||||||
*/
|
*/
|
||||||
define(['base', 'ide/pdfng/directives/pdfViewer'], (App, pdfViewer) =>
|
define(['base', 'ide/pdfng/directives/pdfViewer'], (App, pdfViewer) =>
|
||||||
App.directive('pdfng', [
|
App.directive('pdfng', ($timeout, localStorage) => ({
|
||||||
'$timeout',
|
scope: {
|
||||||
'localStorage',
|
pdfSrc: '=',
|
||||||
($timeout, localStorage) => ({
|
highlights: '=',
|
||||||
scope: {
|
position: '=',
|
||||||
pdfSrc: '=',
|
dblClickCallback: '='
|
||||||
highlights: '=',
|
},
|
||||||
position: '=',
|
link(scope, element, attrs) {
|
||||||
dblClickCallback: '='
|
scope.loading = false
|
||||||
},
|
scope.pleaseJumpTo = null
|
||||||
link(scope, element, attrs) {
|
scope.scale = null
|
||||||
scope.loading = false
|
let initializedPosition = false
|
||||||
scope.pleaseJumpTo = null
|
const initializePosition = function() {
|
||||||
scope.scale = null
|
let position, scale
|
||||||
let initializedPosition = false
|
if (initializedPosition) {
|
||||||
const initializePosition = function() {
|
return
|
||||||
let position, scale
|
}
|
||||||
if (initializedPosition) {
|
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
|
return
|
||||||
}
|
}
|
||||||
initializedPosition = true
|
scope.progress = Math.floor((progress.loaded / progress.total) * 100)
|
||||||
|
if (scope.progress > 100) {
|
||||||
if ((scale = localStorage('pdf.scale')) != null) {
|
scope.progress = 100
|
||||||
scope.scale = { scaleMode: scale.scaleMode, scale: +scale.scale }
|
|
||||||
} else {
|
|
||||||
scope.scale = { scaleMode: 'scale_mode_fit_width' }
|
|
||||||
}
|
}
|
||||||
|
if (scope.progress < 0) {
|
||||||
if ((position = localStorage(`pdf.position.${attrs.key}`))) {
|
return (scope.progress = 0)
|
||||||
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() {
|
return scope.$on('$destroy', function() {})
|
||||||
scope.loaded = true
|
},
|
||||||
scope.progress = 100
|
// console.log 'pdfjs destroy event'
|
||||||
return $timeout(function() {
|
|
||||||
scope.loading = false
|
|
||||||
return delete scope.progress
|
|
||||||
}, 500)
|
|
||||||
})
|
|
||||||
|
|
||||||
scope.fitToHeight = function() {
|
template: `\
|
||||||
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: `\
|
|
||||||
<div data-pdf-viewer class="pdfjs-viewer" pdf-src='pdfSrc' position='position' scale='scale' highlights='highlights' please-jump-to='pleaseJumpTo'></div>
|
<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="pdfjs-controls" ng-class="{'flash': flashControls }">
|
||||||
<div class="btn-group">
|
<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 class="progress-bar" ng-style="{ 'width': progress + '%' }"></div>
|
||||||
</div>\
|
</div>\
|
||||||
`
|
`
|
||||||
})
|
})))
|
||||||
]))
|
|
||||||
|
|
|
@ -17,13 +17,9 @@
|
||||||
define(['base'], App =>
|
define(['base'], App =>
|
||||||
// App = angular.module 'pdfPage', ['pdfHighlights']
|
// App = angular.module 'pdfPage', ['pdfHighlights']
|
||||||
|
|
||||||
App.directive('pdfPage', [
|
App.directive('pdfPage', ($timeout, pdfHighlights, pdfSpinner) => ({
|
||||||
'$timeout',
|
require: '^pdfViewer',
|
||||||
'pdfHighlights',
|
template: `\
|
||||||
'pdfSpinner',
|
|
||||||
($timeout, pdfHighlights, pdfSpinner) => ({
|
|
||||||
require: '^pdfViewer',
|
|
||||||
template: `\
|
|
||||||
<div class="plv-page-view page-view">
|
<div class="plv-page-view page-view">
|
||||||
<div class="pdf-canvas pdfng-empty"></div>
|
<div class="pdf-canvas pdfng-empty"></div>
|
||||||
<div class="plv-text-layer text-layer"></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 class="plv-highlights-layer highlights-layer"></div>
|
||||||
</div>\
|
</div>\
|
||||||
`,
|
`,
|
||||||
link(scope, element, attrs, ctrl) {
|
link(scope, element, attrs, ctrl) {
|
||||||
const canvasElement = $(element).find('.pdf-canvas')
|
const canvasElement = $(element).find('.pdf-canvas')
|
||||||
const textElement = $(element).find('.text-layer')
|
const textElement = $(element).find('.text-layer')
|
||||||
const annotationsElement = $(element).find('.annotations-layer')
|
const annotationsElement = $(element).find('.annotations-layer')
|
||||||
const highlightsElement = $(element).find('.highlights-layer')
|
const highlightsElement = $(element).find('.highlights-layer')
|
||||||
|
|
||||||
const updatePageSize = function(size) {
|
const updatePageSize = function(size) {
|
||||||
const h = Math.floor(size[0])
|
const h = Math.floor(size[0])
|
||||||
const w = Math.floor(size[1])
|
const w = Math.floor(size[1])
|
||||||
element.height(h)
|
element.height(h)
|
||||||
element.width(w)
|
element.width(w)
|
||||||
canvasElement.height(h)
|
canvasElement.height(h)
|
||||||
canvasElement.width(w)
|
canvasElement.width(w)
|
||||||
return (scope.page.sized = true)
|
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()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
})
|
|
||||||
]))
|
// 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) =>
|
define(['base', 'pdfjs-dist/build/pdf'], (App, PDFJS) =>
|
||||||
// App = angular.module 'PDFRenderer', ['pdfAnnotations', 'pdfTextLayer']
|
// App = angular.module 'PDFRenderer', ['pdfAnnotations', 'pdfTextLayer']
|
||||||
|
|
||||||
App.factory('PDFRenderer', [
|
App.factory('PDFRenderer', function(
|
||||||
'$q',
|
$q,
|
||||||
'$timeout',
|
$timeout,
|
||||||
'pdfAnnotations',
|
pdfAnnotations,
|
||||||
'pdfTextLayer',
|
pdfTextLayer,
|
||||||
'pdfSpinner',
|
pdfSpinner
|
||||||
function($q, $timeout, pdfAnnotations, pdfTextLayer, pdfSpinner) {
|
) {
|
||||||
let PDFRenderer
|
let PDFRenderer
|
||||||
return (PDFRenderer = (function() {
|
return (PDFRenderer = (function() {
|
||||||
PDFRenderer = class PDFRenderer {
|
PDFRenderer = class PDFRenderer {
|
||||||
static initClass() {
|
static initClass() {
|
||||||
this.prototype.JOB_QUEUE_INTERVAL = 25
|
this.prototype.JOB_QUEUE_INTERVAL = 25
|
||||||
this.prototype.PAGE_LOAD_TIMEOUT = 60 * 1000
|
this.prototype.PAGE_LOAD_TIMEOUT = 60 * 1000
|
||||||
this.prototype.INDICATOR_DELAY1 = 100 // time to delay before showing the indicator
|
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.INDICATOR_DELAY2 = 250 // time until the indicator starts animating
|
||||||
this.prototype.TEXTLAYER_TIMEOUT = 100
|
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
|
||||||
}
|
}
|
||||||
|
this.pdfjs = PDFJS.getDocument({
|
||||||
constructor(url, options) {
|
url: this.url,
|
||||||
// set up external character mappings - needed for Japanese etc
|
cMapUrl: window.pdfCMapsPath,
|
||||||
this.url = url
|
cMapPacked: true,
|
||||||
this.options = options
|
disableFontFace,
|
||||||
|
// Enable fetching with Range headers to restrict individual
|
||||||
this.scale = this.options.scale || 1
|
// requests to 128kb.
|
||||||
|
// To do this correctly we must:
|
||||||
let disableFontFace
|
// a) disable auto-fetching of the whole file upfront
|
||||||
if (
|
// b) disable streaming (which in this context means streaming of
|
||||||
__guard__(
|
// the response into memory). This isn't supported when using
|
||||||
window.location != null ? window.location.search : undefined,
|
// Range headers, but shouldn't be a problem since we are already
|
||||||
x => x.indexOf('disable-font-face=true')
|
// limiting individual response size through chunked range
|
||||||
) >= 0
|
// requests
|
||||||
) {
|
rangeChunkSize: 128 * 1024,
|
||||||
disableFontFace = true
|
disableAutoFetch: !!this.options.disableAutoFetch,
|
||||||
} else {
|
disableStream: !!this.options.disableAutoFetch
|
||||||
disableFontFace = false
|
})
|
||||||
}
|
this.pdfjs.onProgress = this.options.progressCallback
|
||||||
this.pdfjs = PDFJS.getDocument({
|
this.document = $q.when(this.pdfjs)
|
||||||
url: this.url,
|
this.navigateFn = this.options.navigateFn
|
||||||
cMapUrl: window.pdfCMapsPath,
|
this.spinner = new pdfSpinner()
|
||||||
cMapPacked: true,
|
this.resetState()
|
||||||
disableFontFace,
|
this.document.then(pdfDocument => {
|
||||||
// Enable fetching with Range headers to restrict individual
|
return pdfDocument.getDownloadInfo().then(() => {
|
||||||
// requests to 128kb.
|
return this.options.loadedCallback()
|
||||||
// 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.errorCallback = this.options.errorCallback
|
||||||
this.navigateFn = this.options.navigateFn
|
this.pageSizeChangeCallback = this.options.pageSizeChangeCallback
|
||||||
this.spinner = new pdfSpinner()
|
this.pdfjs.promise.catch(exception => {
|
||||||
this.resetState()
|
// error getting document
|
||||||
this.document.then(pdfDocument => {
|
return this.errorCallback(exception)
|
||||||
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)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
resetState() {
|
resetState() {
|
||||||
this.renderQueue = []
|
this.renderQueue = []
|
||||||
if (this.queueTimer != null) {
|
if (this.queueTimer != null) {
|
||||||
clearTimeout(this.queueTimer)
|
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)
|
|
||||||
}
|
}
|
||||||
|
// clear any existing timers, render tasks
|
||||||
getNumPages() {
|
for (let timer of Array.from(this.spinTimer || [])) {
|
||||||
return this.document.then(pdfDocument => pdfDocument.numPages)
|
clearTimeout(timer)
|
||||||
}
|
}
|
||||||
|
for (let page of Array.from(this.pageState || [])) {
|
||||||
getPage(pageNum) {
|
__guard__(page != null ? page.loadTask : undefined, x => x.cancel())
|
||||||
return this.document.then(pdfDocument =>
|
__guard__(page != null ? page.renderTask : undefined, x1 =>
|
||||||
pdfDocument.getPage(pageNum)
|
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) {
|
getNumPages() {
|
||||||
if (scale == null) {
|
return this.document.then(pdfDocument => pdfDocument.numPages)
|
||||||
;({ scale } = this)
|
}
|
||||||
}
|
|
||||||
return this.document.then(pdfDocument => {
|
getPage(pageNum) {
|
||||||
return pdfDocument.getPage(pageNum).then(
|
return this.document.then(pdfDocument => pdfDocument.getPage(pageNum))
|
||||||
function(page) {
|
}
|
||||||
let viewport
|
|
||||||
return (viewport = page.getViewport(scale))
|
getPdfViewport(pageNum, scale) {
|
||||||
},
|
if (scale == null) {
|
||||||
error => {
|
;({ scale } = this)
|
||||||
return typeof this.errorCallback === 'function'
|
|
||||||
? this.errorCallback(error)
|
|
||||||
: undefined
|
|
||||||
}
|
|
||||||
)
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
return this.document.then(pdfDocument => {
|
||||||
getDestinations() {
|
return pdfDocument.getPage(pageNum).then(
|
||||||
return this.document.then(pdfDocument =>
|
function(page) {
|
||||||
pdfDocument.getDestinations()
|
let viewport
|
||||||
)
|
return (viewport = page.getViewport(scale))
|
||||||
}
|
},
|
||||||
|
|
||||||
getDestination(dest) {
|
|
||||||
return this.document.then(
|
|
||||||
pdfDocument => pdfDocument.getDestination(dest),
|
|
||||||
error => {
|
error => {
|
||||||
return typeof this.errorCallback === 'function'
|
return typeof this.errorCallback === 'function'
|
||||||
? this.errorCallback(error)
|
? this.errorCallback(error)
|
||||||
: undefined
|
: undefined
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
})
|
||||||
|
}
|
||||||
|
|
||||||
getPageIndex(ref) {
|
getDestinations() {
|
||||||
return this.document.then(pdfDocument => {
|
return this.document.then(pdfDocument =>
|
||||||
return pdfDocument.getPageIndex(ref).then(
|
pdfDocument.getDestinations()
|
||||||
idx => idx,
|
)
|
||||||
error => {
|
}
|
||||||
return typeof this.errorCallback === 'function'
|
|
||||||
? this.errorCallback(error)
|
|
||||||
: undefined
|
|
||||||
}
|
|
||||||
)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
getScale() {
|
getDestination(dest) {
|
||||||
return this.scale
|
return this.document.then(
|
||||||
}
|
pdfDocument => pdfDocument.getDestination(dest),
|
||||||
|
error => {
|
||||||
setScale(scale) {
|
return typeof this.errorCallback === 'function'
|
||||||
this.scale = scale
|
? this.errorCallback(error)
|
||||||
return this.resetState()
|
: undefined
|
||||||
}
|
|
||||||
|
|
||||||
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) {
|
getPageIndex(ref) {
|
||||||
this.jobs = this.jobs - 1
|
return this.document.then(pdfDocument => {
|
||||||
return this.triggerRenderQueue(0)
|
return pdfDocument.getPageIndex(ref).then(
|
||||||
}
|
idx => idx,
|
||||||
|
error => {
|
||||||
|
return typeof this.errorCallback === 'function'
|
||||||
|
? this.errorCallback(error)
|
||||||
|
: undefined
|
||||||
|
}
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
renderPages(pages) {
|
getScale() {
|
||||||
if (this.shuttingDown) {
|
return this.scale
|
||||||
return
|
}
|
||||||
}
|
|
||||||
this.renderQueue = Array.from(pages).map(page => ({
|
|
||||||
element: page.elementChildren,
|
|
||||||
pagenum: page.pageNum
|
|
||||||
}))
|
|
||||||
return this.triggerRenderQueue()
|
|
||||||
}
|
|
||||||
|
|
||||||
renderPage(page) {
|
setScale(scale) {
|
||||||
if (this.shuttingDown) {
|
this.scale = scale
|
||||||
return
|
return this.resetState()
|
||||||
}
|
}
|
||||||
const current = {
|
|
||||||
element: page.elementChildren,
|
triggerRenderQueue(interval) {
|
||||||
pagenum: page.pageNum
|
if (interval == null) {
|
||||||
}
|
interval = this.JOB_QUEUE_INTERVAL
|
||||||
this.renderQueue.push(current)
|
}
|
||||||
|
if (this.queueTimer != null) {
|
||||||
|
clearTimeout(this.queueTimer)
|
||||||
|
}
|
||||||
|
return (this.queueTimer = setTimeout(() => {
|
||||||
|
this.queueTimer = null
|
||||||
return this.processRenderQueue()
|
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) {
|
renderPage(page) {
|
||||||
return [page.element.canvas, page.pagenum]
|
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() {
|
// handle the loading indicators for each page
|
||||||
// make an array of the pages in the queue
|
|
||||||
this.queuedPages = []
|
startIndicators() {
|
||||||
for (var page of Array.from(this.renderQueue)) {
|
// make an array of the pages in the queue
|
||||||
this.queuedPages[page.pagenum] = true
|
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) {
|
// add indicators for any new pages in the current queue
|
||||||
if (!this.queuedPages[pagenum]) {
|
return (() => {
|
||||||
clearTimeout(this.spinTimer[pagenum])
|
const result = []
|
||||||
delete this.spinTimer[pagenum]
|
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 result
|
||||||
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
|
|
||||||
})()
|
|
||||||
}
|
|
||||||
|
|
||||||
startIndicator(page) {
|
startIndicator(page) {
|
||||||
const [canvas, pagenum] = Array.from(this.getPageDetails(page))
|
const [canvas, pagenum] = Array.from(this.getPageDetails(page))
|
||||||
canvas.addClass('pdfng-loading')
|
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(() => {
|
return (this.spinTimer[pagenum] = setTimeout(() => {
|
||||||
for (let queuedPage of Array.from(this.renderQueue)) {
|
this.spinner.start(canvas)
|
||||||
if (pagenum === queuedPage.pagenum) {
|
|
||||||
this.spinner.add(canvas, { static: true })
|
|
||||||
this.spinTimerDone[pagenum] = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return delete this.spinTimer[pagenum]
|
return delete this.spinTimer[pagenum]
|
||||||
}, this.INDICATOR_DELAY1))
|
}, this.INDICATOR_DELAY2))
|
||||||
}
|
} else {
|
||||||
|
// stop the existing spin timer
|
||||||
updateIndicator(page) {
|
clearTimeout(this.spinTimer[pagenum])
|
||||||
const [canvas, pagenum] = Array.from(this.getPageDetails(page))
|
// start a new one which will also start spinning
|
||||||
// did the spinner insert itself already?
|
return (this.spinTimer[pagenum] = setTimeout(() => {
|
||||||
if (this.spinTimerDone[pagenum]) {
|
this.spinner.add(canvas, { static: true })
|
||||||
|
this.spinTimerDone[pagenum] = true
|
||||||
return (this.spinTimer[pagenum] = setTimeout(() => {
|
return (this.spinTimer[pagenum] = setTimeout(() => {
|
||||||
this.spinner.start(canvas)
|
this.spinner.start(canvas)
|
||||||
return delete this.spinTimer[pagenum]
|
return delete this.spinTimer[pagenum]
|
||||||
}, this.INDICATOR_DELAY2))
|
}, this.INDICATOR_DELAY2))
|
||||||
} else {
|
}, this.INDICATOR_DELAY1))
|
||||||
// stop the existing spin timer
|
}
|
||||||
clearTimeout(this.spinTimer[pagenum])
|
}
|
||||||
// start a new one which will also start spinning
|
|
||||||
return (this.spinTimer[pagenum] = setTimeout(() => {
|
clearIndicator(page) {
|
||||||
this.spinner.add(canvas, { static: true })
|
const [canvas, pagenum] = Array.from(this.getPageDetails(page))
|
||||||
this.spinTimerDone[pagenum] = true
|
this.spinner.stop(canvas)
|
||||||
return (this.spinTimer[pagenum] = setTimeout(() => {
|
clearTimeout(this.spinTimer[pagenum])
|
||||||
this.spinner.start(canvas)
|
delete this.spinTimer[pagenum]
|
||||||
return delete this.spinTimer[pagenum]
|
return (this.spinTimerDone[pagenum] = true)
|
||||||
}, this.INDICATOR_DELAY2))
|
}
|
||||||
}, this.INDICATOR_DELAY1))
|
|
||||||
}
|
// 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) {
|
this.pageState[pagenum] = pageState = { loadTask }
|
||||||
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
|
return loadTask
|
||||||
|
.then(pageObject => {
|
||||||
processRenderQueue() {
|
// page load success
|
||||||
let pageState
|
$timeout.cancel(timer)
|
||||||
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) {
|
if (loadTask.cancelled) {
|
||||||
return
|
return
|
||||||
} // return from cancelled page load
|
} // return from cancelled page load
|
||||||
__guardMethod__(window.Raven, 'captureMessage', o =>
|
pageState.renderTask = this.doRender(element, pagenum, pageObject)
|
||||||
o.captureMessage(
|
return pageState.renderTask.then(
|
||||||
`pdfng page load timed out after ${this.PAGE_LOAD_TIMEOUT}ms`
|
() => {
|
||||||
)
|
// 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)
|
.catch(error => {
|
||||||
// @jobs = @jobs - 1
|
// page load error
|
||||||
// @triggerRenderQueue(0)
|
$timeout.cancel(timer)
|
||||||
return typeof this.errorCallback === 'function'
|
return this.clearIndicator(page)
|
||||||
? this.errorCallback('timeout')
|
})
|
||||||
: undefined
|
}
|
||||||
}, this.PAGE_LOAD_TIMEOUT)
|
|
||||||
|
|
||||||
var loadTask = this.getPage(pagenum)
|
doRender(element, pagenum, page) {
|
||||||
|
const self = this
|
||||||
|
const { scale } = this
|
||||||
|
|
||||||
loadTask.cancel = function() {
|
if (scale == null) {
|
||||||
return (this.cancelled = true)
|
// scale is undefined, returning
|
||||||
}
|
return
|
||||||
|
|
||||||
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)
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
doRender(element, pagenum, page) {
|
const canvas = $(
|
||||||
const self = this
|
'<canvas class="pdf-canvas pdfng-rendering"></canvas>'
|
||||||
const { scale } = this
|
)
|
||||||
|
// 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) {
|
const viewport = page.getViewport(scale)
|
||||||
// scale is undefined, returning
|
|
||||||
return
|
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 = $(
|
const textLayer = new pdfTextLayer({
|
||||||
'<canvas class="pdf-canvas pdfng-rendering"></canvas>'
|
textLayerDiv: element.text[0],
|
||||||
)
|
viewport,
|
||||||
// In Windows+IE we must have the canvas in the DOM during
|
renderer: PDFJS.renderTextLayer
|
||||||
// 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 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 textLayerTimeout = this.TEXTLAYER_TIMEOUT
|
||||||
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
|
result
|
||||||
const scaledHeight = (Math.floor(viewport.height) * pixelRatio) | 0
|
.then(function() {
|
||||||
|
// page render success
|
||||||
const newWidth = Math.floor(viewport.width)
|
canvas.removeClass('pdfng-rendering')
|
||||||
const newHeight = Math.floor(viewport.height)
|
page.getTextContent({ normalizeWhitespace: true }).then(
|
||||||
|
function(textContent) {
|
||||||
canvas[0].height = scaledHeight
|
textLayer.setTextContent(textContent)
|
||||||
canvas[0].width = scaledWidth
|
return textLayer.render(textLayerTimeout)
|
||||||
|
},
|
||||||
canvas.height(newHeight + 'px')
|
error =>
|
||||||
canvas.width(newWidth + 'px')
|
typeof self.errorCallback === 'function'
|
||||||
|
? self.errorCallback(error)
|
||||||
const oldHeight = element.canvas.height()
|
: undefined
|
||||||
const oldWidth = element.canvas.width()
|
)
|
||||||
if (newHeight !== oldHeight || newWidth !== oldWidth) {
|
return page
|
||||||
element.canvas.height(newHeight + 'px')
|
.getAnnotations()
|
||||||
element.canvas.width(newWidth + 'px')
|
.then(
|
||||||
element.container.height(newHeight + 'px')
|
annotations => annotationsLayer.setAnnotations(annotations),
|
||||||
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)
|
|
||||||
},
|
|
||||||
error =>
|
error =>
|
||||||
typeof self.errorCallback === 'function'
|
typeof self.errorCallback === 'function'
|
||||||
? self.errorCallback(error)
|
? self.errorCallback(error)
|
||||||
: undefined
|
: 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) {
|
function __guard__(value, transform) {
|
||||||
return typeof value !== 'undefined' && value !== null
|
return typeof value !== 'undefined' && value !== null
|
||||||
|
|
|
@ -13,35 +13,33 @@
|
||||||
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
|
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
|
||||||
*/
|
*/
|
||||||
define(['base'], App =>
|
define(['base'], App =>
|
||||||
App.factory('pdfSpinner', [
|
App.factory('pdfSpinner', function() {
|
||||||
function() {
|
let pdfSpinner
|
||||||
let pdfSpinner
|
return (pdfSpinner = class pdfSpinner {
|
||||||
return (pdfSpinner = class pdfSpinner {
|
constructor() {}
|
||||||
constructor() {}
|
// handler for spinners
|
||||||
// handler for spinners
|
|
||||||
|
|
||||||
add(element, options) {
|
add(element, options) {
|
||||||
const size = 64
|
const size = 64
|
||||||
const spinner = $(
|
const spinner = $(
|
||||||
`<div class="pdfng-spinner" style="position: absolute; top: 50%; left:50%; transform: translateX(-50%) translateY(-50%);"><i class="fa fa-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'
|
(options != null ? options.static : undefined) ? '' : ' fa-spin'
|
||||||
}" style="color: #999"></i></div>`
|
}" style="color: #999"></i></div>`
|
||||||
)
|
)
|
||||||
spinner.css({ 'font-size': size + 'px' })
|
spinner.css({ 'font-size': size + 'px' })
|
||||||
return element.append(spinner)
|
return element.append(spinner)
|
||||||
}
|
}
|
||||||
|
|
||||||
start(element) {
|
start(element) {
|
||||||
return element.find('.fa-spinner').addClass('fa-spin')
|
return element.find('.fa-spinner').addClass('fa-spin')
|
||||||
}
|
}
|
||||||
|
|
||||||
stop(element) {
|
stop(element) {
|
||||||
return element.find('.fa-spinner').removeClass('fa-spin')
|
return element.find('.fa-spinner').removeClass('fa-spin')
|
||||||
}
|
}
|
||||||
|
|
||||||
remove(element) {
|
remove(element) {
|
||||||
return element.find('.fa-spinner').remove()
|
return element.find('.fa-spinner').remove()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}))
|
||||||
]))
|
|
||||||
|
|
|
@ -16,65 +16,63 @@ define(['base'], App =>
|
||||||
// uses the PDFJS text layer renderer to provide invisible overlayed
|
// uses the PDFJS text layer renderer to provide invisible overlayed
|
||||||
// text for searching
|
// text for searching
|
||||||
|
|
||||||
App.factory('pdfTextLayer', [
|
App.factory('pdfTextLayer', function() {
|
||||||
function() {
|
let pdfTextLayer
|
||||||
let pdfTextLayer
|
return (pdfTextLayer = class pdfTextLayer {
|
||||||
return (pdfTextLayer = class pdfTextLayer {
|
constructor(options) {
|
||||||
constructor(options) {
|
this.textLayerDiv = options.textLayerDiv
|
||||||
this.textLayerDiv = options.textLayerDiv
|
this.divContentDone = false
|
||||||
this.divContentDone = false
|
this.viewport = options.viewport
|
||||||
this.viewport = options.viewport
|
this.textDivs = []
|
||||||
this.textDivs = []
|
this.renderer = options.renderer
|
||||||
this.renderer = options.renderer
|
this.renderingDone = false
|
||||||
this.renderingDone = false
|
}
|
||||||
|
|
||||||
|
render(timeout) {
|
||||||
|
if (this.renderingDone || !this.divContentDone) {
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
render(timeout) {
|
if (this.textLayerRenderTask != null) {
|
||||||
if (this.renderingDone || !this.divContentDone) {
|
this.textLayerRenderTask.cancel()
|
||||||
return
|
this.textLayerRenderTask = null
|
||||||
}
|
|
||||||
|
|
||||||
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
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
setTextContent(textContent) {
|
this.textDivs = []
|
||||||
if (this.textLayerRenderTask) {
|
const textLayerFrag = document.createDocumentFragment()
|
||||||
this.textLayerRenderTask.cancel()
|
|
||||||
this.textLayerRenderTask = null
|
|
||||||
}
|
|
||||||
|
|
||||||
this.textContent = textContent
|
this.textLayerRenderTask = this.renderer({
|
||||||
return (this.divContentDone = true)
|
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) {
|
define(['base'], function(App) {
|
||||||
// We create and provide this as service so that we can access the global ide
|
// We create and provide this as service so that we can access the global ide
|
||||||
// from within other parts of the angular app.
|
// from within other parts of the angular app.
|
||||||
App.factory('ide', [
|
App.factory('ide', function(
|
||||||
'$http',
|
$http,
|
||||||
'queuedHttp',
|
queuedHttp,
|
||||||
'$modal',
|
$modal,
|
||||||
'$q',
|
$q,
|
||||||
'$filter',
|
$filter,
|
||||||
'$timeout',
|
$timeout
|
||||||
function($http, queuedHttp, $modal, $q, $filter, $timeout) {
|
) {
|
||||||
const ide = {}
|
const ide = {}
|
||||||
ide.$http = $http
|
ide.$http = $http
|
||||||
ide.queuedHttp = queuedHttp
|
ide.queuedHttp = queuedHttp
|
||||||
ide.$q = $q
|
ide.$q = $q
|
||||||
ide.$filter = $filter
|
ide.$filter = $filter
|
||||||
ide.$timeout = $timeout
|
ide.$timeout = $timeout
|
||||||
|
|
||||||
this.recentEvents = []
|
this.recentEvents = []
|
||||||
ide.pushEvent = (type, meta) => {
|
ide.pushEvent = (type, meta) => {
|
||||||
if (meta == null) {
|
if (meta == null) {
|
||||||
meta = {}
|
meta = {}
|
||||||
}
|
|
||||||
sl_console.log('event', type, meta)
|
|
||||||
this.recentEvents.push({ type, meta, date: new Date() })
|
|
||||||
if (this.recentEvents.length > 100) {
|
|
||||||
return this.recentEvents.shift()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
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) => {
|
ide.reportError = (error, meta) => {
|
||||||
if (meta == null) {
|
if (meta == null) {
|
||||||
meta = {}
|
meta = {}
|
||||||
}
|
}
|
||||||
meta.client_id = __guard__(
|
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,
|
this.socket != null ? this.socket.socket : undefined,
|
||||||
x => x.sessionid
|
x2 => x2.transport
|
||||||
)
|
),
|
||||||
meta.transport = __guard__(
|
x1 => x1.name
|
||||||
__guard__(
|
)
|
||||||
this.socket != null ? this.socket.socket : undefined,
|
meta.client_now = new Date()
|
||||||
x2 => x2.transport
|
meta.recent_events = this.recentEvents
|
||||||
),
|
const errorObj = {}
|
||||||
x1 => x1.name
|
if (typeof error === 'object') {
|
||||||
)
|
for (let key of Array.from(Object.getOwnPropertyNames(error))) {
|
||||||
meta.client_now = new Date()
|
errorObj[key] = error[key]
|
||||||
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
|
|
||||||
}
|
}
|
||||||
return $http.post('/error/client', {
|
} else if (typeof error === 'string') {
|
||||||
error: errorObj,
|
errorObj.message = error
|
||||||
meta,
|
|
||||||
_csrf: window.csrfToken
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
return $http.post('/error/client', {
|
||||||
|
error: errorObj,
|
||||||
|
meta,
|
||||||
|
_csrf: window.csrfToken
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
ide.showGenericMessageModal = (title, message) =>
|
ide.showGenericMessageModal = (title, message) =>
|
||||||
$modal.open({
|
$modal.open({
|
||||||
templateUrl: 'genericMessageModalTemplate',
|
templateUrl: 'genericMessageModalTemplate',
|
||||||
controller: 'GenericMessageModalController',
|
controller: 'GenericMessageModalController',
|
||||||
resolve: {
|
resolve: {
|
||||||
title() {
|
title() {
|
||||||
return 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
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
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', [
|
return ide
|
||||||
'$scope',
|
})
|
||||||
'$modalInstance',
|
|
||||||
'title',
|
|
||||||
'message',
|
|
||||||
function($scope, $modalInstance, title, message) {
|
|
||||||
$scope.title = title
|
|
||||||
$scope.message = message
|
|
||||||
|
|
||||||
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) {
|
function __guard__(value, transform) {
|
||||||
|
|
|
@ -14,68 +14,67 @@
|
||||||
*/
|
*/
|
||||||
define(['base'], function(App) {
|
define(['base'], function(App) {
|
||||||
const MAX_PROJECT_NAME_LENGTH = 150
|
const MAX_PROJECT_NAME_LENGTH = 150
|
||||||
return App.controller('ProjectNameController', [
|
return App.controller('ProjectNameController', function(
|
||||||
'$scope',
|
$scope,
|
||||||
'$element',
|
$element,
|
||||||
'settings',
|
settings,
|
||||||
'ide',
|
ide
|
||||||
function($scope, $element, settings, ide) {
|
) {
|
||||||
const projectNameReadOnlyEl = $element.find('.name')[0]
|
const projectNameReadOnlyEl = $element.find('.name')[0]
|
||||||
|
|
||||||
$scope.state = {
|
$scope.state = {
|
||||||
renaming: false,
|
renaming: false,
|
||||||
overflowed: 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.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
|
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
|
||||||
*/
|
*/
|
||||||
define(['base'], App =>
|
define(['base'], App =>
|
||||||
App.controller('SettingsController', [
|
App.controller('SettingsController', function($scope, settings, ide, _) {
|
||||||
'$scope',
|
$scope.overallThemesList = window.overallThemes
|
||||||
'settings',
|
$scope.ui = { loadingStyleSheet: false }
|
||||||
'ide',
|
|
||||||
'_',
|
|
||||||
function($scope, settings, ide, _) {
|
|
||||||
$scope.overallThemesList = window.overallThemes
|
|
||||||
$scope.ui = { loadingStyleSheet: false }
|
|
||||||
|
|
||||||
const _updateCSSFile = function(theme) {
|
const _updateCSSFile = function(theme) {
|
||||||
$scope.ui.loadingStyleSheet = true
|
$scope.ui.loadingStyleSheet = true
|
||||||
const docHeadEl = document.querySelector('head')
|
const docHeadEl = document.querySelector('head')
|
||||||
const oldStyleSheetEl = document.getElementById('main-stylesheet')
|
const oldStyleSheetEl = document.getElementById('main-stylesheet')
|
||||||
const newStyleSheetEl = document.createElement('link')
|
const newStyleSheetEl = document.createElement('link')
|
||||||
newStyleSheetEl.addEventListener('load', e => {
|
newStyleSheetEl.addEventListener('load', e => {
|
||||||
return $scope.$applyAsync(() => {
|
return $scope.$applyAsync(() => {
|
||||||
$scope.ui.loadingStyleSheet = false
|
$scope.ui.loadingStyleSheet = false
|
||||||
return docHeadEl.removeChild(oldStyleSheetEl)
|
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
|
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
|
||||||
*/
|
*/
|
||||||
define(['base'], App =>
|
define(['base'], App =>
|
||||||
App.factory('settings', [
|
App.factory('settings', (ide, event_tracking) => ({
|
||||||
'ide',
|
saveSettings(data) {
|
||||||
'event_tracking',
|
// Tracking code.
|
||||||
(ide, event_tracking) => ({
|
for (let key of Array.from(Object.keys(data))) {
|
||||||
saveSettings(data) {
|
const changedSetting = key
|
||||||
// Tracking code.
|
const changedSettingVal = data[key]
|
||||||
for (let key of Array.from(Object.keys(data))) {
|
event_tracking.sendMB('setting-changed', {
|
||||||
const changedSetting = key
|
changedSetting,
|
||||||
const changedSettingVal = data[key]
|
changedSettingVal
|
||||||
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)
|
|
||||||
}
|
}
|
||||||
})
|
// 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
|
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
|
||||||
*/
|
*/
|
||||||
define(['base'], App =>
|
define(['base'], App =>
|
||||||
App.controller('ShareController', [
|
App.controller('ShareController', function(
|
||||||
'$scope',
|
$scope,
|
||||||
'$modal',
|
$modal,
|
||||||
'ide',
|
ide,
|
||||||
'projectInvites',
|
projectInvites,
|
||||||
'projectMembers',
|
projectMembers,
|
||||||
'event_tracking',
|
event_tracking
|
||||||
function(
|
) {
|
||||||
$scope,
|
$scope.openShareProjectModal = function(isAdmin) {
|
||||||
$modal,
|
$scope.isAdmin = isAdmin
|
||||||
ide,
|
event_tracking.sendMBOnce('ide-open-share-modal-once')
|
||||||
projectInvites,
|
|
||||||
projectMembers,
|
|
||||||
event_tracking
|
|
||||||
) {
|
|
||||||
$scope.openShareProjectModal = function(isAdmin) {
|
|
||||||
$scope.isAdmin = isAdmin
|
|
||||||
event_tracking.sendMBOnce('ide-open-share-modal-once')
|
|
||||||
|
|
||||||
return $modal.open({
|
return $modal.open({
|
||||||
templateUrl: 'shareProjectModalTemplate',
|
templateUrl: 'shareProjectModalTemplate',
|
||||||
controller: 'ShareProjectModalController',
|
controller: 'ShareProjectModalController',
|
||||||
scope: $scope
|
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')
|
|
||||||
})
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
]))
|
|
||||||
|
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
|
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
|
||||||
*/
|
*/
|
||||||
define(['base'], App =>
|
define(['base'], App =>
|
||||||
App.factory('projectInvites', [
|
App.factory('projectInvites', (ide, $http) => ({
|
||||||
'ide',
|
sendInvite(email, privileges, grecaptchaResponse) {
|
||||||
'$http',
|
return $http.post(`/project/${ide.project_id}/invite`, {
|
||||||
(ide, $http) => ({
|
email,
|
||||||
sendInvite(email, privileges, grecaptchaResponse) {
|
privileges,
|
||||||
return $http.post(`/project/${ide.project_id}/invite`, {
|
_csrf: window.csrfToken,
|
||||||
email,
|
'g-recaptcha-response': grecaptchaResponse
|
||||||
privileges,
|
})
|
||||||
_csrf: window.csrfToken,
|
},
|
||||||
'g-recaptcha-response': grecaptchaResponse
|
|
||||||
})
|
|
||||||
},
|
|
||||||
|
|
||||||
revokeInvite(inviteId) {
|
revokeInvite(inviteId) {
|
||||||
return $http({
|
return $http({
|
||||||
url: `/project/${ide.project_id}/invite/${inviteId}`,
|
url: `/project/${ide.project_id}/invite/${inviteId}`,
|
||||||
method: 'DELETE',
|
method: 'DELETE',
|
||||||
headers: {
|
headers: {
|
||||||
'X-Csrf-Token': window.csrfToken
|
'X-Csrf-Token': window.csrfToken
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
|
||||||
resendInvite(inviteId, privileges) {
|
resendInvite(inviteId, privileges) {
|
||||||
return $http.post(
|
return $http.post(
|
||||||
`/project/${ide.project_id}/invite/${inviteId}/resend`,
|
`/project/${ide.project_id}/invite/${inviteId}/resend`,
|
||||||
{
|
{
|
||||||
_csrf: window.csrfToken
|
_csrf: window.csrfToken
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
|
|
||||||
getInvites() {
|
getInvites() {
|
||||||
return $http.get(`/project/${ide.project_id}/invites`, {
|
return $http.get(`/project/${ide.project_id}/invites`, {
|
||||||
json: true,
|
json: true,
|
||||||
headers: {
|
headers: {
|
||||||
'X-Csrf-Token': window.csrfToken
|
'X-Csrf-Token': window.csrfToken
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
})
|
})))
|
||||||
]))
|
|
||||||
|
|
|
@ -10,35 +10,31 @@
|
||||||
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
|
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
|
||||||
*/
|
*/
|
||||||
define(['base'], App =>
|
define(['base'], App =>
|
||||||
App.factory('projectMembers', [
|
App.factory('projectMembers', (ide, $http) => ({
|
||||||
'ide',
|
removeMember(member) {
|
||||||
'$http',
|
return $http({
|
||||||
(ide, $http) => ({
|
url: `/project/${ide.project_id}/users/${member._id}`,
|
||||||
removeMember(member) {
|
method: 'DELETE',
|
||||||
return $http({
|
headers: {
|
||||||
url: `/project/${ide.project_id}/users/${member._id}`,
|
'X-Csrf-Token': window.csrfToken
|
||||||
method: 'DELETE',
|
}
|
||||||
headers: {
|
})
|
||||||
'X-Csrf-Token': window.csrfToken
|
},
|
||||||
}
|
|
||||||
})
|
|
||||||
},
|
|
||||||
|
|
||||||
addGroup(group_id, privileges) {
|
addGroup(group_id, privileges) {
|
||||||
return $http.post(`/project/${ide.project_id}/group`, {
|
return $http.post(`/project/${ide.project_id}/group`, {
|
||||||
group_id,
|
group_id,
|
||||||
privileges,
|
privileges,
|
||||||
_csrf: window.csrfToken
|
_csrf: window.csrfToken
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
|
||||||
getMembers() {
|
getMembers() {
|
||||||
return $http.get(`/project/${ide.project_id}/members`, {
|
return $http.get(`/project/${ide.project_id}/members`, {
|
||||||
json: true,
|
json: true,
|
||||||
headers: {
|
headers: {
|
||||||
'X-Csrf-Token': window.csrfToken
|
'X-Csrf-Token': window.csrfToken
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
})
|
})))
|
||||||
]))
|
|
||||||
|
|
|
@ -14,129 +14,120 @@
|
||||||
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
|
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
|
||||||
*/
|
*/
|
||||||
define(['base'], function(App) {
|
define(['base'], function(App) {
|
||||||
App.controller('AccountSettingsController', [
|
App.controller('AccountSettingsController', function(
|
||||||
'$scope',
|
$scope,
|
||||||
'$http',
|
$http,
|
||||||
'$modal',
|
$modal,
|
||||||
'event_tracking',
|
event_tracking,
|
||||||
'UserAffiliationsDataService',
|
UserAffiliationsDataService,
|
||||||
'UserOauthDataService',
|
UserOauthDataService
|
||||||
function(
|
) {
|
||||||
$scope,
|
$scope.subscribed = true
|
||||||
$http,
|
|
||||||
$modal,
|
|
||||||
event_tracking,
|
|
||||||
UserAffiliationsDataService,
|
|
||||||
UserOauthDataService
|
|
||||||
) {
|
|
||||||
$scope.subscribed = true
|
|
||||||
|
|
||||||
$scope.unsubscribe = function() {
|
$scope.unsubscribe = function() {
|
||||||
$scope.unsubscribing = true
|
$scope.unsubscribing = true
|
||||||
return $http({
|
return $http({
|
||||||
method: 'DELETE',
|
method: 'DELETE',
|
||||||
url: '/user/newsletter/unsubscribe',
|
url: '/user/newsletter/unsubscribe',
|
||||||
headers: {
|
headers: {
|
||||||
'X-CSRF-Token': window.csrfToken
|
'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', [
|
return ($scope.cancel = () => $modalInstance.dismiss('cancel'))
|
||||||
'$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'))
|
|
||||||
}
|
|
||||||
])
|
|
||||||
})
|
})
|
||||||
|
|
|
@ -15,250 +15,249 @@
|
||||||
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
|
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
|
||||||
*/
|
*/
|
||||||
define(['base'], App =>
|
define(['base'], App =>
|
||||||
App.controller('UserAffiliationsController', [
|
App.controller('UserAffiliationsController', function(
|
||||||
'$scope',
|
$scope,
|
||||||
'UserAffiliationsDataService',
|
UserAffiliationsDataService,
|
||||||
'$q',
|
$q,
|
||||||
'_',
|
_
|
||||||
function($scope, UserAffiliationsDataService, $q, _) {
|
) {
|
||||||
$scope.userEmails = []
|
$scope.userEmails = []
|
||||||
|
|
||||||
const LOCAL_AND_DOMAIN_REGEX = /([^@]+)@(.+)/
|
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 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 _matchLocalAndDomain = function(userEmailInput) {
|
||||||
const match =
|
const match =
|
||||||
userEmailInput != null
|
userEmailInput != null
|
||||||
? userEmailInput.match(LOCAL_AND_DOMAIN_REGEX)
|
? userEmailInput.match(LOCAL_AND_DOMAIN_REGEX)
|
||||||
: undefined
|
: undefined
|
||||||
if (match != null) {
|
if (match != null) {
|
||||||
return { local: match[1], domain: match[2] }
|
return { local: match[1], domain: match[2] }
|
||||||
} else {
|
} else {
|
||||||
return { local: null, domain: null }
|
return { local: null, domain: null }
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
$scope.getEmailSuggestion = function(userInput) {
|
$scope.getEmailSuggestion = function(userInput) {
|
||||||
const userInputLocalAndDomain = _matchLocalAndDomain(userInput)
|
const userInputLocalAndDomain = _matchLocalAndDomain(userInput)
|
||||||
$scope.ui.isValidEmail = EMAIL_REGEX.test(userInput)
|
$scope.ui.isValidEmail = EMAIL_REGEX.test(userInput)
|
||||||
$scope.ui.isBlacklistedEmail = false
|
$scope.ui.isBlacklistedEmail = false
|
||||||
$scope.ui.showManualUniversitySelectionUI = false
|
$scope.ui.showManualUniversitySelectionUI = false
|
||||||
if (userInputLocalAndDomain.domain != null) {
|
if (userInputLocalAndDomain.domain != null) {
|
||||||
$scope.ui.isBlacklistedEmail = UserAffiliationsDataService.isDomainBlacklisted(
|
$scope.ui.isBlacklistedEmail = UserAffiliationsDataService.isDomainBlacklisted(
|
||||||
userInputLocalAndDomain.domain
|
userInputLocalAndDomain.domain
|
||||||
)
|
)
|
||||||
return UserAffiliationsDataService.getUniversityDomainFromPartialDomainInput(
|
return UserAffiliationsDataService.getUniversityDomainFromPartialDomainInput(
|
||||||
userInputLocalAndDomain.domain
|
userInputLocalAndDomain.domain
|
||||||
)
|
)
|
||||||
.then(function(universityDomain) {
|
.then(function(universityDomain) {
|
||||||
const currentUserInputLocalAndDomain = _matchLocalAndDomain(
|
const currentUserInputLocalAndDomain = _matchLocalAndDomain(
|
||||||
$scope.newAffiliation.email
|
$scope.newAffiliation.email
|
||||||
)
|
)
|
||||||
if (
|
if (
|
||||||
currentUserInputLocalAndDomain.domain ===
|
currentUserInputLocalAndDomain.domain ===
|
||||||
universityDomain.hostname
|
universityDomain.hostname
|
||||||
) {
|
) {
|
||||||
$scope.newAffiliation.university = universityDomain.university
|
$scope.newAffiliation.university = universityDomain.university
|
||||||
$scope.newAffiliation.department = universityDomain.department
|
$scope.newAffiliation.department = universityDomain.department
|
||||||
} else {
|
} else {
|
||||||
$scope.newAffiliation.university = null
|
|
||||||
$scope.newAffiliation.department = null
|
|
||||||
}
|
|
||||||
return $q.resolve(
|
|
||||||
`${userInputLocalAndDomain.local}@${universityDomain.hostname}`
|
|
||||||
)
|
|
||||||
})
|
|
||||||
.catch(function() {
|
|
||||||
$scope.newAffiliation.university = null
|
$scope.newAffiliation.university = null
|
||||||
$scope.newAffiliation.department = null
|
$scope.newAffiliation.department = null
|
||||||
return $q.reject(null)
|
}
|
||||||
})
|
return $q.resolve(
|
||||||
} else {
|
`${userInputLocalAndDomain.local}@${universityDomain.hostname}`
|
||||||
$scope.newAffiliation.university = null
|
)
|
||||||
$scope.newAffiliation.department = null
|
})
|
||||||
return $q.reject(null)
|
.catch(function() {
|
||||||
}
|
$scope.newAffiliation.university = null
|
||||||
}
|
$scope.newAffiliation.department = null
|
||||||
|
return $q.reject(null)
|
||||||
$scope.selectUniversityManually = function() {
|
})
|
||||||
|
} else {
|
||||||
$scope.newAffiliation.university = null
|
$scope.newAffiliation.university = null
|
||||||
$scope.newAffiliation.department = null
|
$scope.newAffiliation.department = null
|
||||||
return ($scope.ui.showManualUniversitySelectionUI = true)
|
return $q.reject(null)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
$scope.changeAffiliation = function(userEmail) {
|
$scope.selectUniversityManually = function() {
|
||||||
if (
|
$scope.newAffiliation.university = null
|
||||||
__guard__(
|
$scope.newAffiliation.department = null
|
||||||
userEmail.affiliation != null
|
return ($scope.ui.showManualUniversitySelectionUI = true)
|
||||||
? userEmail.affiliation.institution
|
}
|
||||||
: undefined,
|
|
||||||
x => x.id
|
|
||||||
) != null
|
|
||||||
) {
|
|
||||||
UserAffiliationsDataService.getUniversityDetails(
|
|
||||||
userEmail.affiliation.institution.id
|
|
||||||
).then(
|
|
||||||
universityDetails =>
|
|
||||||
($scope.affiliationToChange.university = universityDetails)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
$scope.affiliationToChange.email = userEmail.email
|
$scope.changeAffiliation = function(userEmail) {
|
||||||
$scope.affiliationToChange.role = userEmail.affiliation.role
|
if (
|
||||||
return ($scope.affiliationToChange.department =
|
__guard__(
|
||||||
userEmail.affiliation.department)
|
userEmail.affiliation != null
|
||||||
}
|
? userEmail.affiliation.institution
|
||||||
|
: undefined,
|
||||||
$scope.saveAffiliationChange = function(userEmail) {
|
x => x.id
|
||||||
userEmail.affiliation.role = $scope.affiliationToChange.role
|
) != null
|
||||||
userEmail.affiliation.department = $scope.affiliationToChange.department
|
) {
|
||||||
_resetAffiliationToChange()
|
UserAffiliationsDataService.getUniversityDetails(
|
||||||
return _monitorRequest(
|
userEmail.affiliation.institution.id
|
||||||
UserAffiliationsDataService.addRoleAndDepartment(
|
).then(
|
||||||
userEmail.email,
|
universityDetails =>
|
||||||
userEmail.affiliation.role,
|
($scope.affiliationToChange.university = universityDetails)
|
||||||
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.resendConfirmationEmail = function(userEmail) {
|
$scope.affiliationToChange.email = userEmail.email
|
||||||
$scope.ui.isResendingConfirmation = true
|
$scope.affiliationToChange.role = userEmail.affiliation.role
|
||||||
return _monitorRequest(
|
return ($scope.affiliationToChange.department =
|
||||||
UserAffiliationsDataService.resendConfirmationEmail(userEmail.email)
|
userEmail.affiliation.department)
|
||||||
).finally(() => ($scope.ui.isResendingConfirmation = false))
|
}
|
||||||
}
|
|
||||||
|
|
||||||
$scope.acknowledgeError = function() {
|
$scope.saveAffiliationChange = function(userEmail) {
|
||||||
_reset()
|
userEmail.affiliation.role = $scope.affiliationToChange.role
|
||||||
return _getUserEmails()
|
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.cancelAffiliationChange = email => _resetAffiliationToChange()
|
||||||
($scope.affiliationToChange = {
|
|
||||||
email: '',
|
|
||||||
university: null,
|
|
||||||
role: null,
|
|
||||||
department: null
|
|
||||||
})
|
|
||||||
|
|
||||||
var _resetNewAffiliation = () =>
|
$scope.isChangingAffiliation = email =>
|
||||||
($scope.newAffiliation = {
|
$scope.affiliationToChange.email === email
|
||||||
email: '',
|
|
||||||
country: null,
|
|
||||||
university: null,
|
|
||||||
role: null,
|
|
||||||
department: null
|
|
||||||
})
|
|
||||||
|
|
||||||
var _resetAddingEmail = function() {
|
$scope.showAddEmailForm = () => ($scope.ui.showAddEmailUI = true)
|
||||||
$scope.ui.showAddEmailUI = false
|
|
||||||
$scope.ui.isValidEmail = false
|
|
||||||
$scope.ui.isBlacklistedEmail = false
|
|
||||||
return ($scope.ui.showManualUniversitySelectionUI = false)
|
|
||||||
}
|
|
||||||
|
|
||||||
var _reset = function() {
|
$scope.addNewEmail = function() {
|
||||||
$scope.ui = {
|
let addEmailPromise
|
||||||
hasError: false,
|
if ($scope.newAffiliation.university == null) {
|
||||||
errorMessage: '',
|
addEmailPromise = UserAffiliationsDataService.addUserEmail(
|
||||||
showChangeAffiliationUI: false,
|
$scope.newAffiliation.email
|
||||||
isMakingRequest: false,
|
)
|
||||||
isLoadingEmails: false,
|
} else {
|
||||||
isAddingNewEmail: false,
|
if ($scope.newAffiliation.university.isUserSuggested) {
|
||||||
isResendingConfirmation: false
|
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()
|
_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()
|
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) {
|
function __guard__(value, transform) {
|
||||||
return typeof value !== 'undefined' && value !== null
|
return typeof value !== 'undefined' && value !== null
|
||||||
? transform(value)
|
? transform(value)
|
||||||
|
|
|
@ -414,151 +414,145 @@ define(['base'], function(App) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return App.factory('UserAffiliationsDataService', [
|
return App.factory('UserAffiliationsDataService', function($http, $q, _) {
|
||||||
'$http',
|
const getCountries = () => $q.resolve(countriesList)
|
||||||
'$q',
|
|
||||||
'_',
|
|
||||||
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 = () =>
|
const getUserEmails = () =>
|
||||||
$http.get('/user/emails').then(response => response.data)
|
$http.get('/user/emails').then(response => response.data)
|
||||||
|
|
||||||
const getUserDefaultEmail = () =>
|
const getUserDefaultEmail = () =>
|
||||||
getUserEmails().then(userEmails =>
|
getUserEmails().then(userEmails =>
|
||||||
_.find(userEmails, userEmail => userEmail.default)
|
_.find(userEmails, userEmail => userEmail.default)
|
||||||
)
|
)
|
||||||
|
|
||||||
const getUniversitiesFromCountry = function(country) {
|
const getUniversitiesFromCountry = function(country) {
|
||||||
let universitiesFromCountry
|
let universitiesFromCountry
|
||||||
if (universities[country.code] != null) {
|
if (universities[country.code] != null) {
|
||||||
universitiesFromCountry = universities[country.code]
|
universitiesFromCountry = universities[country.code]
|
||||||
} else {
|
} else {
|
||||||
universitiesFromCountry = $http
|
universitiesFromCountry = $http
|
||||||
.get('/institutions/list', {
|
.get('/institutions/list', {
|
||||||
params: { country_code: country.code }
|
params: { country_code: country.code }
|
||||||
})
|
})
|
||||||
.then(response => (universities[country.code] = response.data))
|
.then(response => (universities[country.code] = response.data))
|
||||||
}
|
|
||||||
return $q.resolve(universitiesFromCountry)
|
|
||||||
}
|
}
|
||||||
|
return $q.resolve(universitiesFromCountry)
|
||||||
|
}
|
||||||
|
|
||||||
const getUniversityDomainFromPartialDomainInput = function(
|
const getUniversityDomainFromPartialDomainInput = function(
|
||||||
partialDomainInput
|
partialDomainInput
|
||||||
) {
|
) {
|
||||||
if (universitiesByDomain[partialDomainInput] != null) {
|
if (universitiesByDomain[partialDomainInput] != null) {
|
||||||
return $q.resolve(universitiesByDomain[partialDomainInput])
|
return $q.resolve(universitiesByDomain[partialDomainInput])
|
||||||
} else {
|
} else {
|
||||||
return $http
|
return $http
|
||||||
.get('/institutions/domains', {
|
.get('/institutions/domains', {
|
||||||
params: { hostname: partialDomainInput, limit: 1 }
|
params: { hostname: partialDomainInput, limit: 1 }
|
||||||
})
|
})
|
||||||
.then(function(response) {
|
.then(function(response) {
|
||||||
const university = response.data[0]
|
const university = response.data[0]
|
||||||
if (
|
if (
|
||||||
university != null &&
|
university != null &&
|
||||||
!isDomainBlacklisted(university.hostname)
|
!isDomainBlacklisted(university.hostname)
|
||||||
) {
|
) {
|
||||||
universitiesByDomain[university.hostname] = university
|
universitiesByDomain[university.hostname] = university
|
||||||
return $q.resolve(university)
|
return $q.resolve(university)
|
||||||
} else {
|
} else {
|
||||||
return $q.reject(null)
|
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 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
|
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
|
||||||
*/
|
*/
|
||||||
define(['base'], App =>
|
define(['base'], App =>
|
||||||
App.controller('ClearSessionsController', [
|
App.controller('ClearSessionsController', function($scope, $http) {
|
||||||
'$scope',
|
$scope.state = {
|
||||||
'$http',
|
otherSessions: window.otherSessions,
|
||||||
function($scope, $http) {
|
error: false,
|
||||||
$scope.state = {
|
success: false
|
||||||
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))
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
]))
|
|
||||||
|
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 =>
|
define(['base'], App =>
|
||||||
App.controller('UserOauthController', [
|
App.controller('UserOauthController', function(
|
||||||
'$http',
|
$http,
|
||||||
'$scope',
|
$scope,
|
||||||
'$q',
|
$q,
|
||||||
'_',
|
_,
|
||||||
'UserOauthDataService',
|
UserOauthDataService
|
||||||
function($http, $scope, $q, _, UserOauthDataService) {
|
) {
|
||||||
const _monitorRequest = function(promise) {
|
const _monitorRequest = function(promise) {
|
||||||
$scope.ui.hasError = false
|
$scope.ui.hasError = false
|
||||||
$scope.ui.isLoadingV1Ids = true
|
$scope.ui.isLoadingV1Ids = true
|
||||||
promise
|
promise
|
||||||
.catch(response => {
|
.catch(response => {
|
||||||
$scope.ui.hasError = true
|
$scope.ui.hasError = true
|
||||||
$scope.ui.errorMessage =
|
$scope.ui.errorMessage =
|
||||||
response && response.data && response.data.message
|
response && response.data && response.data.message
|
||||||
? response.data.message
|
? response.data.message
|
||||||
: 'error'
|
: 'error'
|
||||||
})
|
})
|
||||||
.finally(() => {
|
.finally(() => {
|
||||||
$scope.ui.isLoadingV1Ids = false
|
$scope.ui.isLoadingV1Ids = false
|
||||||
})
|
})
|
||||||
return promise
|
return promise
|
||||||
|
}
|
||||||
|
const _reset = function() {
|
||||||
|
$scope.ui = {
|
||||||
|
hasError: false,
|
||||||
|
errorMessage: '',
|
||||||
|
isLoadingV1Ids: false
|
||||||
}
|
}
|
||||||
const _reset = function() {
|
$scope.providers = window.oauthProviders
|
||||||
$scope.ui = {
|
$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,
|
hasError: false,
|
||||||
errorMessage: '',
|
isProcessing: true
|
||||||
isLoadingV1Ids: false
|
|
||||||
}
|
}
|
||||||
$scope.providers = window.oauthProviders
|
// Data for update
|
||||||
$scope.thirdPartyIds = window.thirdPartyIds
|
const data = {
|
||||||
// until oauthUseV2=true, we will use OAuth data via v1 DB,
|
_csrf: window.csrfToken,
|
||||||
// except for Collabratec, which is only writing to the v2 DB.
|
link: false,
|
||||||
// $scope.v2ThirdPartyIds is required for Collabratec,
|
providerId
|
||||||
// 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)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
$http
|
||||||
|
.post('/user/oauth-unlink', data)
|
||||||
_reset()
|
.catch(error => {
|
||||||
if (!window.oauthUseV2) {
|
$scope.providers[providerId].ui.isProcessing = false
|
||||||
_getUserV1OauthProviders()
|
_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) {
|
define(['base'], function(App) {
|
||||||
return App.factory('UserOauthDataService', [
|
return App.factory('UserOauthDataService', function($http) {
|
||||||
'$http',
|
const getUserOauthV1 = () => {
|
||||||
function($http) {
|
if (window.ExposedSettings.isOverleaf) {
|
||||||
const getUserOauthV1 = () => {
|
return $http.get('/user/v1-oauth-uids').then(response => {
|
||||||
if (window.ExposedSettings.isOverleaf) {
|
return response.data
|
||||||
return $http.get('/user/v1-oauth-uids').then(response => {
|
})
|
||||||
return response.data
|
} else {
|
||||||
})
|
return {}
|
||||||
} else {
|
|
||||||
return {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
getUserOauthV1
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
])
|
|
||||||
|
return {
|
||||||
|
getUserOauthV1
|
||||||
|
}
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
|
@ -2,33 +2,31 @@
|
||||||
camelcase
|
camelcase
|
||||||
*/
|
*/
|
||||||
define(['base'], App =>
|
define(['base'], App =>
|
||||||
App.service('ProjectListService', [
|
App.service('ProjectListService', () => ({
|
||||||
() => ({
|
getOwnerName(project) {
|
||||||
getOwnerName(project) {
|
if (project.accessLevel === 'owner') {
|
||||||
if (project.accessLevel === 'owner') {
|
return 'You'
|
||||||
return 'You'
|
} else if (project.owner != null) {
|
||||||
} else if (project.owner != null) {
|
return this.getUserName(project.owner)
|
||||||
return this.getUserName(project.owner)
|
} else {
|
||||||
} else {
|
return 'None'
|
||||||
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'
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
})
|
},
|
||||||
]))
|
|
||||||
|
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
|
// Interceptor to check auth failures in all $http requests
|
||||||
// http://bahmutov.calepin.co/catch-all-errors-in-angular-app.html
|
// http://bahmutov.calepin.co/catch-all-errors-in-angular-app.html
|
||||||
|
|
||||||
app.factory('unAuthHttpResponseInterceptor', [
|
app.factory('unAuthHttpResponseInterceptor', ($q, $location) => ({
|
||||||
'$q',
|
responseError(response) {
|
||||||
'$location',
|
// redirect any unauthorised or forbidden responses back to /login
|
||||||
($q, $location) => ({
|
//
|
||||||
responseError(response) {
|
// set disableAutoLoginRedirect:true in the http request config
|
||||||
// redirect any unauthorised or forbidden responses back to /login
|
// to disable this behaviour
|
||||||
//
|
if (
|
||||||
// set disableAutoLoginRedirect:true in the http request config
|
[401, 403].includes(response.status) &&
|
||||||
// to disable this behaviour
|
!(response.config != null
|
||||||
if (
|
? response.config.disableAutoLoginRedirect
|
||||||
[401, 403].includes(response.status) &&
|
: undefined)
|
||||||
!(response.config != null
|
) {
|
||||||
? response.config.disableAutoLoginRedirect
|
// for /project urls set the ?redir parameter to come back here
|
||||||
: undefined)
|
// otherwise just go to the login page
|
||||||
) {
|
if (window.location.pathname.match(/^\/project/)) {
|
||||||
// for /project urls set the ?redir parameter to come back here
|
window.location = `/login?redir=${encodeURI(window.location.pathname)}`
|
||||||
// otherwise just go to the login page
|
} else {
|
||||||
if (window.location.pathname.match(/^\/project/)) {
|
window.location = '/login'
|
||||||
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([
|
app.config([
|
||||||
'$httpProvider',
|
'$httpProvider',
|
||||||
|
|
Loading…
Reference in a new issue