EditorManager.js 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321
  1. /* eslint-disable
  2. camelcase,
  3. handle-callback-err,
  4. max-len,
  5. no-return-assign,
  6. no-undef,
  7. */
  8. // TODO: This file was created by bulk-decaffeinate.
  9. // Fix any style issues and re-enable lint.
  10. /*
  11. * decaffeinate suggestions:
  12. * DS102: Remove unnecessary code created because of implicit returns
  13. * DS206: Consider reworking classes to avoid initClass
  14. * DS207: Consider shorter variations of null checks
  15. * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
  16. */
  17. define([
  18. 'ide/editor/Document',
  19. 'ide/editor/components/spellMenu',
  20. 'ide/editor/directives/aceEditor',
  21. 'ide/editor/directives/toggleSwitch',
  22. 'ide/editor/controllers/SavingNotificationController'
  23. ], function(Document) {
  24. let EditorManager
  25. return (EditorManager = (function() {
  26. EditorManager = class EditorManager {
  27. static initClass() {
  28. this.prototype._syncTimeout = null
  29. }
  30. constructor(ide, $scope, localStorage) {
  31. this.ide = ide
  32. this.$scope = $scope
  33. this.localStorage = localStorage
  34. this.$scope.editor = {
  35. sharejs_doc: null,
  36. open_doc_id: null,
  37. open_doc_name: null,
  38. opening: true,
  39. trackChanges: false,
  40. wantTrackChanges: false,
  41. showRichText: this.showRichText()
  42. }
  43. this.$scope.$on('entity:selected', (event, entity) => {
  44. if (this.$scope.ui.view !== 'history' && entity.type === 'doc') {
  45. return this.openDoc(entity)
  46. }
  47. })
  48. this.$scope.$on('entity:deleted', (event, entity) => {
  49. if (this.$scope.editor.open_doc_id === entity.id) {
  50. if (!this.$scope.project.rootDoc_id) {
  51. this.$scope.ui.view = null
  52. return
  53. }
  54. const doc = this.ide.fileTreeManager.findEntityById(
  55. this.$scope.project.rootDoc_id
  56. )
  57. if (doc == null) {
  58. this.$scope.ui.view = null
  59. return
  60. }
  61. return this.openDoc(doc)
  62. }
  63. })
  64. let initialized = false
  65. this.$scope.$on('file-tree:initialized', () => {
  66. if (!initialized) {
  67. initialized = true
  68. return this.autoOpenDoc()
  69. }
  70. })
  71. this.$scope.$on('flush-changes', () => {
  72. return Document.flushAll()
  73. })
  74. this.$scope.$watch('editor.wantTrackChanges', value => {
  75. if (value == null) {
  76. return
  77. }
  78. return this._syncTrackChangesState(this.$scope.editor.sharejs_doc)
  79. })
  80. }
  81. showRichText() {
  82. return (
  83. this.localStorage(`editor.mode.${this.$scope.project_id}`) ===
  84. 'rich-text'
  85. )
  86. }
  87. autoOpenDoc() {
  88. const open_doc_id =
  89. this.ide.localStorage(`doc.open_id.${this.$scope.project_id}`) ||
  90. this.$scope.project.rootDoc_id
  91. if (open_doc_id == null) {
  92. return
  93. }
  94. const doc = this.ide.fileTreeManager.findEntityById(open_doc_id)
  95. if (doc == null) {
  96. return
  97. }
  98. return this.openDoc(doc)
  99. }
  100. openDocId(doc_id, options) {
  101. if (options == null) {
  102. options = {}
  103. }
  104. const doc = this.ide.fileTreeManager.findEntityById(doc_id)
  105. if (doc == null) {
  106. return
  107. }
  108. return this.openDoc(doc, options)
  109. }
  110. openDoc(doc, options) {
  111. if (options == null) {
  112. options = {}
  113. }
  114. sl_console.log(`[openDoc] Opening ${doc.id}`)
  115. this.$scope.ui.view = 'editor'
  116. const done = () => {
  117. if (options.gotoLine != null) {
  118. // allow Ace to display document before moving, delay until next tick
  119. // added delay to make this happen later that gotoStoredPosition in
  120. // CursorPositionManager
  121. return setTimeout(() => {
  122. return this.$scope.$broadcast(
  123. 'editor:gotoLine',
  124. options.gotoLine,
  125. options.gotoColumn
  126. )
  127. }, 0)
  128. } else if (options.gotoOffset != null) {
  129. return setTimeout(() => {
  130. return this.$scope.$broadcast(
  131. 'editor:gotoOffset',
  132. options.gotoOffset
  133. )
  134. }, 0)
  135. }
  136. }
  137. // If we already have the document open we can return at this point.
  138. // Note: only use forceReopen:true to override this when the document is
  139. // is out of sync and needs to be reloaded from the server.
  140. if (doc.id === this.$scope.editor.open_doc_id && !options.forceReopen) {
  141. // automatically update the file tree whenever the file is opened
  142. this.ide.fileTreeManager.selectEntity(doc)
  143. this.$scope.$apply(() => {
  144. return done()
  145. })
  146. return
  147. }
  148. // We're now either opening a new document or reloading a broken one.
  149. this.$scope.editor.open_doc_id = doc.id
  150. this.$scope.editor.open_doc_name = doc.name
  151. this.ide.localStorage(`doc.open_id.${this.$scope.project_id}`, doc.id)
  152. this.ide.fileTreeManager.selectEntity(doc)
  153. this.$scope.editor.opening = true
  154. return this._openNewDocument(doc, (error, sharejs_doc) => {
  155. if (error != null) {
  156. this.ide.showGenericMessageModal(
  157. 'Error opening document',
  158. 'Sorry, something went wrong opening this document. Please try again.'
  159. )
  160. return
  161. }
  162. this._syncTrackChangesState(sharejs_doc)
  163. this.$scope.$broadcast('doc:opened')
  164. return this.$scope.$apply(() => {
  165. this.$scope.editor.opening = false
  166. this.$scope.editor.sharejs_doc = sharejs_doc
  167. return done()
  168. })
  169. })
  170. }
  171. _openNewDocument(doc, callback) {
  172. if (callback == null) {
  173. callback = function(error, sharejs_doc) {}
  174. }
  175. sl_console.log('[_openNewDocument] Opening...')
  176. const current_sharejs_doc = this.$scope.editor.sharejs_doc
  177. const new_sharejs_doc = Document.getDocument(this.ide, doc.id)
  178. // Leave the current document only when we are opening a different new
  179. // one, to avoid race conditions between leaving and joining the same
  180. // document.
  181. if (
  182. current_sharejs_doc != null &&
  183. current_sharejs_doc !== new_sharejs_doc
  184. ) {
  185. sl_console.log('[_openNewDocument] Leaving existing open doc...')
  186. current_sharejs_doc.leaveAndCleanUp()
  187. this._unbindFromDocumentEvents(current_sharejs_doc)
  188. }
  189. return new_sharejs_doc.join(error => {
  190. if (error != null) {
  191. return callback(error)
  192. }
  193. this._bindToDocumentEvents(doc, new_sharejs_doc)
  194. return callback(null, new_sharejs_doc)
  195. })
  196. }
  197. _bindToDocumentEvents(doc, sharejs_doc) {
  198. sharejs_doc.on('error', (error, meta) => {
  199. let message
  200. if ((error != null ? error.message : undefined) != null) {
  201. ;({ message } = error)
  202. } else if (typeof error === 'string') {
  203. message = error
  204. } else {
  205. message = ''
  206. }
  207. if (/maxDocLength/.test(message)) {
  208. this.ide.showGenericMessageModal(
  209. 'Document Too Long',
  210. 'Sorry, this file is too long to be edited manually. Please upload it directly.'
  211. )
  212. } else if (/too many comments or tracked changes/.test(message)) {
  213. this.ide.showGenericMessageModal(
  214. 'Too many comments or tracked changes',
  215. 'Sorry, this file has too many comments or tracked changes. Please try accepting or rejecting some existing changes, or resolving and deleting some comments.'
  216. )
  217. } else {
  218. this.ide.socket.disconnect()
  219. this.ide.reportError(error, meta)
  220. this.ide.showGenericMessageModal(
  221. 'Out of sync',
  222. "Sorry, this file has gone out of sync and we need to do a full refresh. <br> <a href='/learn/Kb/Editor_out_of_sync_problems'>Please see this help guide for more information</a>"
  223. )
  224. }
  225. return this.openDoc(doc, { forceReopen: true })
  226. })
  227. return sharejs_doc.on('externalUpdate', update => {
  228. if (this._ignoreExternalUpdates) {
  229. return
  230. }
  231. if (
  232. _.property(['meta', 'type'])(update) === 'external' &&
  233. _.property(['meta', 'source'])(update) === 'git-bridge'
  234. ) {
  235. return
  236. }
  237. return this.ide.showGenericMessageModal(
  238. 'Document Updated Externally',
  239. 'This document was just updated externally. Any recent changes you have made may have been overwritten. To see previous versions please look in the history.'
  240. )
  241. })
  242. }
  243. _unbindFromDocumentEvents(document) {
  244. return document.off()
  245. }
  246. getCurrentDocValue() {
  247. return this.$scope.editor.sharejs_doc != null
  248. ? this.$scope.editor.sharejs_doc.getSnapshot()
  249. : undefined
  250. }
  251. getCurrentDocId() {
  252. return this.$scope.editor.open_doc_id
  253. }
  254. startIgnoringExternalUpdates() {
  255. return (this._ignoreExternalUpdates = true)
  256. }
  257. stopIgnoringExternalUpdates() {
  258. return (this._ignoreExternalUpdates = false)
  259. }
  260. _syncTrackChangesState(doc) {
  261. let tryToggle
  262. if (doc == null) {
  263. return
  264. }
  265. if (this._syncTimeout != null) {
  266. clearTimeout(this._syncTimeout)
  267. this._syncTimeout = null
  268. }
  269. const want = this.$scope.editor.wantTrackChanges
  270. const have = doc.getTrackingChanges()
  271. if (want === have) {
  272. this.$scope.editor.trackChanges = want
  273. return
  274. }
  275. return (tryToggle = () => {
  276. const saved =
  277. doc.getInflightOp() == null && doc.getPendingOp() == null
  278. if (saved) {
  279. doc.setTrackingChanges(want)
  280. return this.$scope.$apply(() => {
  281. return (this.$scope.editor.trackChanges = want)
  282. })
  283. } else {
  284. return (this._syncTimeout = setTimeout(tryToggle, 100))
  285. }
  286. })()
  287. }
  288. }
  289. EditorManager.initClass()
  290. return EditorManager
  291. })())
  292. })