ide.js 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448
  1. /* eslint-disable
  2. camelcase,
  3. max-len,
  4. no-cond-assign,
  5. no-return-assign,
  6. no-unused-vars,
  7. no-useless-escape,
  8. */
  9. // TODO: This file was created by bulk-decaffeinate.
  10. // Fix any style issues and re-enable lint.
  11. /*
  12. * decaffeinate suggestions:
  13. * DS101: Remove unnecessary use of Array.from
  14. * DS102: Remove unnecessary code created because of implicit returns
  15. * DS103: Rewrite code to no longer use __guard__
  16. * DS207: Consider shorter variations of null checks
  17. * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
  18. */
  19. import App from './base'
  20. import FileTreeManager from './ide/file-tree/FileTreeManager'
  21. import LoadingManager from './ide/LoadingManager'
  22. import ConnectionManager from './ide/connection/ConnectionManager'
  23. import EditorManager from './ide/editor/EditorManager'
  24. import OnlineUsersManager from './ide/online-users/OnlineUsersManager'
  25. import HistoryManager from './ide/history/HistoryManager'
  26. import HistoryV2Manager from './ide/history/HistoryV2Manager'
  27. import PermissionsManager from './ide/permissions/PermissionsManager'
  28. import BinaryFilesManager from './ide/binary-files/BinaryFilesManager'
  29. import ReferencesManager from './ide/references/ReferencesManager'
  30. import MetadataManager from './ide/metadata/MetadataManager'
  31. import './ide/review-panel/ReviewPanelManager'
  32. import OutlineManager from './features/outline/outline-manager'
  33. import SafariScrollPatcher from './ide/SafariScrollPatcher'
  34. import './ide/cobranding/CobrandingDataService'
  35. import './ide/settings/index'
  36. import './ide/chat/index'
  37. import './ide/clone/index'
  38. import './ide/file-view/index'
  39. import './ide/hotkeys/index'
  40. import './ide/wordcount/index'
  41. import './ide/directives/layout'
  42. import './ide/directives/validFile'
  43. import './ide/directives/verticalResizablePanes'
  44. import './ide/services/ide'
  45. import './directives/focus'
  46. import './directives/fineUpload'
  47. import './directives/scroll'
  48. import './directives/onEnter'
  49. import './directives/stopPropagation'
  50. import './directives/rightClick'
  51. import './directives/expandableTextArea'
  52. import './directives/videoPlayState'
  53. import './services/queued-http'
  54. import './services/validateCaptcha'
  55. import './services/validateCaptchaV3'
  56. import './services/wait-for'
  57. import './filters/formatDate'
  58. import './main/event'
  59. import './main/account-upgrade-angular'
  60. import './main/system-messages'
  61. import '../../modules/modules-ide.js'
  62. import './shared/context/controllers/root-context-controller'
  63. import './features/editor-navigation-toolbar/controllers/editor-navigation-toolbar-controller'
  64. import './features/pdf-preview/controllers/pdf-preview-controller'
  65. import './features/share-project-modal/controllers/react-share-project-modal-controller'
  66. import './features/source-editor/controllers/editor-switch-controller'
  67. import getMeta from './utils/meta'
  68. import { cleanupServiceWorker } from './utils/service-worker-cleanup'
  69. App.controller(
  70. 'IdeController',
  71. function (
  72. $scope,
  73. $timeout,
  74. ide,
  75. localStorage,
  76. eventTracking,
  77. metadata,
  78. $q,
  79. CobrandingDataService,
  80. $window
  81. ) {
  82. // Don't freak out if we're already in an apply callback
  83. let err, pdfLayout, userAgent
  84. $scope.$originalApply = $scope.$apply
  85. $scope.$apply = function (fn) {
  86. if (fn == null) {
  87. fn = function () {}
  88. }
  89. const phase = this.$root.$$phase
  90. if (phase === '$apply' || phase === '$digest') {
  91. return fn()
  92. } else {
  93. return this.$originalApply(fn)
  94. }
  95. }
  96. $scope.state = {
  97. loading: true,
  98. load_progress: 40,
  99. error: null,
  100. }
  101. $scope.ui = {
  102. leftMenuShown: false,
  103. view: 'editor',
  104. chatOpen: false,
  105. pdfLayout: 'sideBySide',
  106. pdfHidden: false,
  107. pdfWidth: 0,
  108. reviewPanelOpen: localStorage(`ui.reviewPanelOpen.${window.project_id}`),
  109. miniReviewPanelVisible: false,
  110. chatResizerSizeOpen: 7,
  111. chatResizerSizeClosed: 0,
  112. }
  113. $scope.user = window.user
  114. $scope.settings = window.userSettings
  115. $scope.anonymous = window.anonymous
  116. $scope.isTokenMember = window.isTokenMember
  117. $scope.isRestrictedTokenMember = window.isRestrictedTokenMember
  118. $scope.cobranding = {
  119. isProjectCobranded: CobrandingDataService.isProjectCobranded(),
  120. logoImgUrl: CobrandingDataService.getLogoImgUrl(),
  121. submitBtnHtml: CobrandingDataService.getSubmitBtnHtml(),
  122. brandVariationName: CobrandingDataService.getBrandVariationName(),
  123. brandVariationHomeUrl: CobrandingDataService.getBrandVariationHomeUrl(),
  124. }
  125. $scope.chat = {}
  126. ide.toggleReviewPanel = $scope.toggleReviewPanel = function () {
  127. $scope.$applyAsync(() => {
  128. if (!$scope.project.features.trackChangesVisible) {
  129. return
  130. }
  131. $scope.ui.reviewPanelOpen = !$scope.ui.reviewPanelOpen
  132. eventTracking.sendMB('rp-toggle-panel', {
  133. value: $scope.ui.reviewPanelOpen,
  134. })
  135. })
  136. }
  137. $scope.$watch('ui.reviewPanelOpen', function (value) {
  138. if (value != null) {
  139. return localStorage(`ui.reviewPanelOpen.${window.project_id}`, value)
  140. }
  141. })
  142. $scope.$on('layout:pdf:resize', function (_, layoutState) {
  143. $scope.ui.pdfHidden = layoutState.east.initClosed
  144. return ($scope.ui.pdfWidth = layoutState.east.size)
  145. })
  146. $scope.$watch('ui.view', function (newView, oldView) {
  147. if (newView !== oldView) {
  148. $scope.$broadcast('layout:flat-screen:toggle')
  149. }
  150. if (newView != null && newView !== 'editor' && newView !== 'pdf') {
  151. eventTracking.sendMBOnce(`ide-open-view-${newView}-once`)
  152. }
  153. })
  154. $scope.$watch('ui.chatOpen', function (isOpen) {
  155. if (isOpen) {
  156. eventTracking.sendMBOnce('ide-open-chat-once')
  157. }
  158. })
  159. $scope.$watch('ui.leftMenuShown', function (isOpen) {
  160. if (isOpen) {
  161. eventTracking.sendMBOnce('ide-open-left-menu-once')
  162. }
  163. })
  164. $scope.trackHover = feature => {
  165. eventTracking.sendMBOnce(`ide-hover-${feature}-once`)
  166. }
  167. // End of tracking code.
  168. window._ide = ide
  169. ide.validFileRegex = '^[^*/]*$' // Don't allow * and /
  170. const useFallbackWebsocket =
  171. window.location &&
  172. window.location.search &&
  173. window.location.search.match(/ws=fallback/)
  174. // if we previously failed to load the websocket fall back to null (the siteUrl)
  175. ide.wsUrl = useFallbackWebsocket ? null : window.sharelatex.wsUrl || null // websocket url (if defined)
  176. ide.project_id = $scope.project_id = window.project_id
  177. ide.$scope = $scope
  178. ide.referencesSearchManager = new ReferencesManager(ide, $scope)
  179. ide.loadingManager = new LoadingManager($scope)
  180. ide.connectionManager = new ConnectionManager(ide, $scope)
  181. ide.fileTreeManager = new FileTreeManager(ide, $scope)
  182. ide.editorManager = new EditorManager(
  183. ide,
  184. $scope,
  185. localStorage,
  186. eventTracking
  187. )
  188. ide.onlineUsersManager = new OnlineUsersManager(ide, $scope)
  189. if (window.data.useV2History) {
  190. ide.historyManager = new HistoryV2Manager(ide, $scope, localStorage)
  191. } else {
  192. ide.historyManager = new HistoryManager(ide, $scope)
  193. }
  194. ide.permissionsManager = new PermissionsManager(ide, $scope)
  195. ide.binaryFilesManager = new BinaryFilesManager(ide, $scope)
  196. ide.metadataManager = new MetadataManager(ide, $scope, metadata)
  197. ide.outlineManager = new OutlineManager(ide, $scope)
  198. let inited = false
  199. $scope.$on('project:joined', function () {
  200. if (inited) {
  201. return
  202. }
  203. inited = true
  204. if (
  205. __guard__(
  206. $scope != null ? $scope.project : undefined,
  207. x => x.deletedByExternalDataSource
  208. )
  209. ) {
  210. ide.showGenericMessageModal(
  211. 'Project Renamed or Deleted',
  212. `\
  213. This project has either been renamed or deleted by an external data source such as Dropbox.
  214. We don't want to delete your data on Overleaf, so this project still contains your history and collaborators.
  215. If the project has been renamed please look in your project list for a new project under the new name.\
  216. `
  217. )
  218. }
  219. return $timeout(function () {
  220. if ($scope.permissions.write) {
  221. let _labelsInitialLoadDone
  222. ide.metadataManager.loadProjectMetaFromServer()
  223. return (_labelsInitialLoadDone = true)
  224. }
  225. }, 200)
  226. })
  227. // Count the first 'doc:opened' as a sign that the ide is loaded
  228. // and broadcast a message. This is a good event to listen for
  229. // if you want to wait until the ide is fully loaded and initialized
  230. let _loaded = false
  231. $scope.$on('doc:opened', function () {
  232. if (_loaded) {
  233. return
  234. }
  235. $scope.$broadcast('ide:loaded')
  236. return (_loaded = true)
  237. })
  238. ide.editingSessionHeartbeat = () => {
  239. const segmentation = {
  240. editorType: ide.editorManager.getEditorType(),
  241. }
  242. eventTracking.editingSessionHeartbeat(segmentation)
  243. }
  244. $scope.$on('cursor:editor:update', () => {
  245. ide.editingSessionHeartbeat()
  246. })
  247. $scope.$on('scroll:editor:update', () => {
  248. ide.editingSessionHeartbeat()
  249. })
  250. angular.element($window).on('click', ide.editingSessionHeartbeat)
  251. $scope.$on('$destroy', () =>
  252. angular.element($window).off('click', ide.editingSessionHeartbeat)
  253. )
  254. const DARK_THEMES = [
  255. 'ambiance',
  256. 'chaos',
  257. 'clouds_midnight',
  258. 'cobalt',
  259. 'idle_fingers',
  260. 'merbivore',
  261. 'merbivore_soft',
  262. 'mono_industrial',
  263. 'monokai',
  264. 'pastel_on_dark',
  265. 'solarized_dark',
  266. 'terminal',
  267. 'tomorrow_night',
  268. 'tomorrow_night_blue',
  269. 'tomorrow_night_bright',
  270. 'tomorrow_night_eighties',
  271. 'twilight',
  272. 'vibrant_ink',
  273. ]
  274. $scope.darkTheme = false
  275. $scope.$watch('settings.editorTheme', function (theme) {
  276. if (Array.from(DARK_THEMES).includes(theme)) {
  277. return ($scope.darkTheme = true)
  278. } else {
  279. return ($scope.darkTheme = false)
  280. }
  281. })
  282. ide.localStorage = localStorage
  283. ide.browserIsSafari = false
  284. $scope.switchToFlatLayout = function (view) {
  285. $scope.ui.pdfLayout = 'flat'
  286. $scope.ui.view = view
  287. return ide.localStorage('pdf.layout', 'flat')
  288. }
  289. $scope.switchToSideBySideLayout = function (view) {
  290. $scope.ui.pdfLayout = 'sideBySide'
  291. $scope.ui.view = view
  292. return localStorage('pdf.layout', 'split')
  293. }
  294. if ((pdfLayout = localStorage('pdf.layout'))) {
  295. if (pdfLayout === 'split') {
  296. $scope.switchToSideBySideLayout()
  297. }
  298. if (pdfLayout === 'flat') {
  299. $scope.switchToFlatLayout()
  300. }
  301. } else {
  302. $scope.switchToSideBySideLayout()
  303. }
  304. // Update ui.pdfOpen when the layout changes.
  305. // The east pane should open when the layout changes from "Editor only" or "PDF only" to "Editor & PDF".
  306. $scope.$watch('ui.pdfLayout', value => {
  307. $scope.ui.pdfOpen = value === 'sideBySide'
  308. })
  309. // Update ui.pdfLayout when the east pane is toggled.
  310. // The layout should be set to "Editor & PDF" (sideBySide) when the east pane is opened, and "Editor only" (flat) when the east pane is closed.
  311. $scope.$watch('ui.pdfOpen', value => {
  312. $scope.ui.pdfLayout = value ? 'sideBySide' : 'flat'
  313. if (value) {
  314. window.dispatchEvent(new CustomEvent('ui:pdf-open'))
  315. }
  316. })
  317. $scope.recompileViaKey = () => {
  318. if ($scope.recompile) {
  319. $scope.recompile()
  320. } else {
  321. window.dispatchEvent(new CustomEvent('pdf:recompile'))
  322. }
  323. }
  324. $scope.handleKeyDown = event => {
  325. if (event.shiftKey || event.altKey) {
  326. return
  327. }
  328. // Ctrl+s or Cmd+s => recompile
  329. if (event.key === 's' && (event.metaKey || event.ctrlKey)) {
  330. event.preventDefault()
  331. $scope.recompileViaKey()
  332. }
  333. }
  334. try {
  335. ;({ userAgent } = navigator)
  336. ide.browserIsSafari =
  337. userAgent &&
  338. /.*Safari\/.*/.test(userAgent) &&
  339. !/.*Chrome\/.*/.test(userAgent) &&
  340. !/.*Chromium\/.*/.test(userAgent)
  341. } catch (error) {
  342. err = error
  343. console.error(err)
  344. }
  345. if (ide.browserIsSafari) {
  346. ide.safariScrollPatcher = new SafariScrollPatcher($scope)
  347. }
  348. // Fix Chrome 61 and 62 text-shadow rendering
  349. let browserIsChrome61or62 = false
  350. try {
  351. const chromeVersion =
  352. parseFloat(navigator.userAgent.split(' Chrome/')[1]) || null
  353. browserIsChrome61or62 = chromeVersion != null
  354. if (browserIsChrome61or62) {
  355. document.styleSheets[0].insertRule(
  356. '.ace_editor.ace_autocomplete .ace_completion-highlight { text-shadow: none !important; font-weight: bold; }',
  357. 1
  358. )
  359. }
  360. } catch (error1) {
  361. err = error1
  362. console.error(err)
  363. }
  364. // User can append ?ft=somefeature to url to activate a feature toggle
  365. ide.featureToggle = __guard__(
  366. __guard__(
  367. typeof location !== 'undefined' && location !== null
  368. ? location.search
  369. : undefined,
  370. x1 => x1.match(/^\?ft=(\w+)$/)
  371. ),
  372. x => x[1]
  373. )
  374. // Listen for editor:lint event from CM6 linter
  375. window.addEventListener('editor:lint', event => {
  376. $scope.hasLintingError = event.detail.hasLintingError
  377. })
  378. return ide.socket.on('project:publicAccessLevel:changed', data => {
  379. if (data.newAccessLevel != null) {
  380. ide.$scope.project.publicAccesLevel = data.newAccessLevel
  381. return $scope.$digest()
  382. }
  383. })
  384. }
  385. )
  386. cleanupServiceWorker()
  387. angular.module('SharelatexApp').config(function ($provide) {
  388. $provide.decorator('$browser', [
  389. '$delegate',
  390. function ($delegate) {
  391. $delegate.onUrlChange = function () {}
  392. $delegate.url = function () {
  393. return ''
  394. }
  395. return $delegate
  396. },
  397. ])
  398. })
  399. export default angular.bootstrap(document.body, ['SharelatexApp'])
  400. function __guard__(value, transform) {
  401. return typeof value !== 'undefined' && value !== null
  402. ? transform(value)
  403. : undefined
  404. }