Просмотр исходного кода

Remove Ace (#14299)

GitOrigin-RevId: ec8788fdbc8aea73ca33ec2810f4e588fe9476b5
Alf Eaton 2 лет назад
Родитель
Сommit
9875e55a27
72 измененных файлов с 59 добавлено и 6860 удалено
  1. 0 13
      package-lock.json
  2. 0 1
      services/web/.eslintrc
  3. 0 8
      services/web/app/src/Features/Project/ProjectController.js
  4. 0 1
      services/web/app/src/infrastructure/PackageVersions.js
  5. 3 8
      services/web/app/views/project/editor/editor-pane.pug
  6. 0 3
      services/web/app/views/project/editor/editor.pug
  7. 0 11
      services/web/app/views/project/editor/file-tree-react.pug
  8. 0 5
      services/web/app/views/project/editor/meta.pug
  9. 1 1
      services/web/app/views/project/editor/review-panel.pug
  10. 0 32
      services/web/app/views/project/editor/source-editor.pug
  11. 0 43
      services/web/app/views/project/editor/toolbar.pug
  12. 23 1
      services/web/frontend/js/features/dictionary/ignored-words.ts
  13. 0 3
      services/web/frontend/js/features/editor-left-menu/components/help-show-hotkeys.tsx
  14. 2 9
      services/web/frontend/js/features/hotkeys-modal/components/hotkeys-modal.jsx
  15. 0 60
      services/web/frontend/js/features/outline/controllers/outline-controller.js
  16. 0 128
      services/web/frontend/js/features/outline/outline-manager.js
  17. 0 133
      services/web/frontend/js/features/outline/outline-parser.js
  18. 0 93
      services/web/frontend/js/features/source-editor/components/cm6-switch-away-survey.tsx
  19. 8 11
      services/web/frontend/js/features/source-editor/components/codemirror-toolbar.tsx
  20. 0 172
      services/web/frontend/js/features/source-editor/components/editor-switch-legacy.tsx
  21. 5 17
      services/web/frontend/js/features/source-editor/components/editor-switch.tsx
  22. 0 81
      services/web/frontend/js/features/source-editor/components/legacy-editor-warning.tsx
  23. 0 9
      services/web/frontend/js/features/source-editor/controllers/cm6-switch-away-survey-controller.js
  24. 0 6
      services/web/frontend/js/features/source-editor/controllers/editor-switch-controller.js
  25. 0 9
      services/web/frontend/js/features/source-editor/controllers/legacy-editor-warning-controller.js
  26. 1 2
      services/web/frontend/js/features/source-editor/extensions/changes/change-manager.ts
  27. 0 11
      services/web/frontend/js/features/source-editor/utils/switch-away-survey.ts
  28. 0 68
      services/web/frontend/js/ide.js
  29. 0 117
      services/web/frontend/js/ide/SafariScrollPatcher.js
  30. 3 32
      services/web/frontend/js/ide/editor/Document.js
  31. 1 34
      services/web/frontend/js/ide/editor/EditorManager.js
  32. 0 13
      services/web/frontend/js/ide/editor/ShareJsDoc.js
  33. 0 159
      services/web/frontend/js/ide/editor/ace-performance.ts
  34. 0 46
      services/web/frontend/js/ide/editor/components/spellMenu.js
  35. 0 1012
      services/web/frontend/js/ide/editor/directives/aceEditor.js
  36. 0 473
      services/web/frontend/js/ide/editor/directives/aceEditor/auto-complete/AutoCompleteManager.js
  37. 0 241
      services/web/frontend/js/ide/editor/directives/aceEditor/auto-complete/CommandManager.js
  38. 0 237
      services/web/frontend/js/ide/editor/directives/aceEditor/auto-complete/EnvironmentManager.js
  39. 0 73
      services/web/frontend/js/ide/editor/directives/aceEditor/auto-complete/Helpers.js
  40. 0 143
      services/web/frontend/js/ide/editor/directives/aceEditor/auto-complete/PackageManager.js
  41. 0 34
      services/web/frontend/js/ide/editor/directives/aceEditor/auto-complete/snippets/Environments.js
  42. 0 699
      services/web/frontend/js/ide/editor/directives/aceEditor/auto-complete/snippets/TopHundredSnippets.js
  43. 0 57
      services/web/frontend/js/ide/editor/directives/aceEditor/cursor-position/CursorPositionAdapter.js
  44. 0 119
      services/web/frontend/js/ide/editor/directives/aceEditor/cursor-position/CursorPositionManager.js
  45. 0 396
      services/web/frontend/js/ide/editor/directives/aceEditor/highlights/HighlightsManager.js
  46. 0 100
      services/web/frontend/js/ide/editor/directives/aceEditor/metadata/MetadataManager.js
  47. 0 137
      services/web/frontend/js/ide/editor/directives/aceEditor/spell-check/HighlightedWordManager.js
  48. 0 22
      services/web/frontend/js/ide/editor/directives/aceEditor/spell-check/IgnoredMisspellings.js
  49. 0 96
      services/web/frontend/js/ide/editor/directives/aceEditor/spell-check/SpellCheckAdapter.js
  50. 0 24
      services/web/frontend/js/ide/editor/directives/aceEditor/spell-check/SpellCheckManager.js
  51. 0 240
      services/web/frontend/js/ide/editor/directives/aceEditor/track-changes/TrackChangesAdapter.js
  52. 0 664
      services/web/frontend/js/ide/editor/directives/aceEditor/track-changes/TrackChangesManager.js
  53. 0 30
      services/web/frontend/js/ide/editor/directives/aceEditor/undo/UndoManager.js
  54. 0 25
      services/web/frontend/js/ide/files/services/files.js
  55. 0 54
      services/web/frontend/js/ide/graphics/services/graphics.js
  56. 0 44
      services/web/frontend/js/ide/preamble/services/preamble.js
  57. 0 21
      services/web/frontend/js/ide/review-panel/controllers/ReviewPanelController.js
  58. 8 18
      services/web/frontend/js/ide/review-panel/directives/reviewPanelSorted.js
  59. 0 7
      services/web/frontend/js/ide/toolbar/EditorLoaderController.js
  60. 0 23
      services/web/frontend/js/ide/toolbar/EditorToolbarController.js
  61. 0 1
      services/web/frontend/js/ide/toolbar/index.js
  62. 1 172
      services/web/frontend/js/vendor/libs/sharejs.js
  63. 0 1
      services/web/frontend/stories/decorators/scope.tsx
  64. 1 1
      services/web/frontend/stories/editor-switch.stories.jsx
  65. 0 105
      services/web/frontend/stylesheets/app/editor.less
  66. 1 13
      services/web/frontend/stylesheets/app/editor/review-panel.less
  67. 0 1
      services/web/package.json
  68. 0 2
      services/web/test/frontend/components/editor-left-menu/scope.tsx
  69. 0 1
      services/web/test/frontend/features/editor-left-menu/components/help-show-hotkeys.test.jsx
  70. 1 13
      services/web/test/frontend/features/hotkeys-modal/components/hotkeys-modal.test.jsx
  71. 0 204
      services/web/test/frontend/features/outline/outline-parser.test.js
  72. 0 17
      services/web/webpack.config.js

+ 0 - 13
package-lock.json

@@ -14497,12 +14497,6 @@
         "node": ">= 0.6"
         "node": ">= 0.6"
       }
       }
     },
     },
-    "node_modules/ace-builds": {
-      "version": "1.4.12",
-      "resolved": "git+ssh://git@github.com/overleaf/ace-builds.git#80aa64e7098fead36c15a3f15c6cc6ca5f0e56b1",
-      "dev": true,
-      "license": "BSD-3-Clause"
-    },
     "node_modules/acorn": {
     "node_modules/acorn": {
       "version": "7.4.1",
       "version": "7.4.1",
       "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz",
       "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz",
@@ -43692,7 +43686,6 @@
         "@uppy/utils": "^4.0.7",
         "@uppy/utils": "^4.0.7",
         "@uppy/xhr-upload": "^1.6.8",
         "@uppy/xhr-upload": "^1.6.8",
         "abort-controller": "^3.0.0",
         "abort-controller": "^3.0.0",
-        "ace-builds": "overleaf/ace-builds#v1.4.12-69aace50e6796d42116f8f96e19d2468d8a88af9",
         "acorn": "^7.1.1",
         "acorn": "^7.1.1",
         "acorn-walk": "^7.1.1",
         "acorn-walk": "^7.1.1",
         "algoliasearch": "^3.35.1",
         "algoliasearch": "^3.35.1",
@@ -52208,7 +52201,6 @@
         "@xmldom/xmldom": "^0.7.13",
         "@xmldom/xmldom": "^0.7.13",
         "abort-controller": "^3.0.0",
         "abort-controller": "^3.0.0",
         "accepts": "^1.3.7",
         "accepts": "^1.3.7",
-        "ace-builds": "overleaf/ace-builds#v1.4.12-69aace50e6796d42116f8f96e19d2468d8a88af9",
         "acorn": "^7.1.1",
         "acorn": "^7.1.1",
         "acorn-walk": "^7.1.1",
         "acorn-walk": "^7.1.1",
         "algoliasearch": "^3.35.1",
         "algoliasearch": "^3.35.1",
@@ -57685,11 +57677,6 @@
         "negotiator": "0.6.3"
         "negotiator": "0.6.3"
       }
       }
     },
     },
-    "ace-builds": {
-      "version": "git+ssh://git@github.com/overleaf/ace-builds.git#80aa64e7098fead36c15a3f15c6cc6ca5f0e56b1",
-      "dev": true,
-      "from": "ace-builds@overleaf/ace-builds#v1.4.12-69aace50e6796d42116f8f96e19d2468d8a88af9"
-    },
     "acorn": {
     "acorn": {
       "version": "7.4.1",
       "version": "7.4.1",
       "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz",
       "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz",

+ 0 - 1
services/web/.eslintrc

@@ -166,7 +166,6 @@
         "__webpack_public_path__": true,
         "__webpack_public_path__": true,
         "$": true,
         "$": true,
         "angular": true,
         "angular": true,
-        "ace": true,
         "ga": true,
         "ga": true,
         // Injected in layout.pug
         // Injected in layout.pug
         "user_id": true,
         "user_id": true,

+ 0 - 8
services/web/app/src/Features/Project/ProjectController.js

@@ -799,11 +799,6 @@ const ProjectController = {
 
 
             const detachRole = req.params.detachRole
             const detachRole = req.params.detachRole
 
 
-            // Allow override via legacy_source_editor=true in query string
-            const showLegacySourceEditor = shouldDisplayFeature(
-              'legacy_source_editor'
-            )
-
             const showSymbolPalette =
             const showSymbolPalette =
               !Features.hasFeature('saas') ||
               !Features.hasFeature('saas') ||
               (user.features && user.features.symbolPalette)
               (user.features && user.features.symbolPalette)
@@ -896,8 +891,6 @@ const ProjectController = {
               showTemplatesServerPro,
               showTemplatesServerPro,
               pdfjsVariant: pdfjsAssignment.variant,
               pdfjsVariant: pdfjsAssignment.variant,
               debugPdfDetach,
               debugPdfDetach,
-              showLegacySourceEditor,
-              showSourceToolbar: !showLegacySourceEditor,
               showSymbolPalette,
               showSymbolPalette,
               symbolPaletteAvailable: Features.hasFeature('symbol-palette'),
               symbolPaletteAvailable: Features.hasFeature('symbol-palette'),
               detachRole,
               detachRole,
@@ -905,7 +898,6 @@ const ProjectController = {
               showUpgradePrompt,
               showUpgradePrompt,
               fixedSizeDocument: true,
               fixedSizeDocument: true,
               useOpenTelemetry: Settings.useOpenTelemetryClient,
               useOpenTelemetry: Settings.useOpenTelemetryClient,
-              showCM6SwitchAwaySurvey: Settings.showCM6SwitchAwaySurvey,
               isReviewPanelReact: reviewPanelAssignment.variant === 'react',
               isReviewPanelReact: reviewPanelAssignment.variant === 'react',
               idePageReact,
               idePageReact,
               showPersonalAccessToken,
               showPersonalAccessToken,

+ 0 - 1
services/web/app/src/infrastructure/PackageVersions.js

@@ -1,6 +1,5 @@
 const version = {
 const version = {
   // Upgrade instructions: https://github.com/overleaf/write_latex/wiki/Upgrading-Ace
   // Upgrade instructions: https://github.com/overleaf/write_latex/wiki/Upgrading-Ace
-  ace: '1.4.12',
   mathjax: '2.7.9',
   mathjax: '2.7.9',
   'mathjax-3': '3.2.2',
   'mathjax-3': '3.2.2',
 }
 }

+ 3 - 8
services/web/app/views/project/editor/editor-pane.pug

@@ -21,7 +21,6 @@
 		include ./file-view
 		include ./file-view
 
 
 		.editor-container.full-size(
 		.editor-container.full-size(
-			class={"has-source-toolbar" : showSourceToolbar},
 			ng-show="ui.view == 'editor' && editor.multiSelectedCount === 0"
 			ng-show="ui.view == 'editor' && editor.multiSelectedCount === 0"
 			vertical-resizable-panes="south-pane-resizer"
 			vertical-resizable-panes="south-pane-resizer"
 			vertical-resizable-panes-hidden-externally-on="south-pane-toggled"
 			vertical-resizable-panes-hidden-externally-on="south-pane-toggled"
@@ -44,14 +43,10 @@
 						|   #{translate("open_a_file_on_the_left")}
 						|   #{translate("open_a_file_on_the_left")}
 
 
 				div(ng-controller="EditorLoaderController")
 				div(ng-controller="EditorLoaderController")
-					if (!showSourceToolbar)
-						include ./toolbar
 
 
-				div(ng-if="editor.newSourceEditor")
-					include ../../source-editor/source-editor
-				div(ng-if="!editor.newSourceEditor")
-					include ./source-editor
-				div(ng-if="!editor.newSourceEditor || !reviewPanel.isReact")
+				include ../../source-editor/source-editor
+				
+				div(ng-if="!reviewPanel.isReact")
 					if !isRestrictedTokenMember
 					if !isRestrictedTokenMember
 						include ./review-panel
 						include ./review-panel
 
 

+ 0 - 3
services/web/app/views/project/editor/editor.pug

@@ -12,9 +12,6 @@ div.full-size(
 	custom-toggler-msg-when-open=translate("tooltip_hide_pdf")
 	custom-toggler-msg-when-open=translate("tooltip_hide_pdf")
 	custom-toggler-msg-when-closed=translate("tooltip_show_pdf")
 	custom-toggler-msg-when-closed=translate("tooltip_show_pdf")
 )
 )
-	if (settings.showCM6SwitchAwaySurvey)
-		cm6-switch-away-survey()
-
 	include ./editor-pane
 	include ./editor-pane
 
 
 	.ui-layout-east
 	.ui-layout-east

+ 0 - 11
services/web/app/views/project/editor/file-tree-react.pug

@@ -24,15 +24,4 @@ aside.editor-sidebar.full-size(
 
 
 	.outline-container(
 	.outline-container(
 		vertical-resizable-bottom
 		vertical-resizable-bottom
-		ng-controller="OutlineController"
 	)
 	)
-		outline-pane(
-			is-tex-file="isTexFile"
-			outline="outline"
-			project-id="project_id"
-			jump-to-line="jumpToLine"
-			on-toggle="onToggle"
-			event-tracking="eventTracking"
-			highlighted-line="highlightedLine"
-			show="show"
-		)

+ 0 - 5
services/web/app/views/project/editor/meta.pug

@@ -13,16 +13,12 @@ meta(name="ol-wikiEnabled" data-type="boolean" content=settings.proxyLearn)
 meta(name="ol-gitBridgePublicBaseUrl" content=gitBridgePublicBaseUrl)
 meta(name="ol-gitBridgePublicBaseUrl" content=gitBridgePublicBaseUrl)
 meta(name="ol-gitBridgeEnabled" data-type="boolean" content=gitBridgeEnabled)
 meta(name="ol-gitBridgeEnabled" data-type="boolean" content=gitBridgeEnabled)
 meta(name="ol-compilesUserContentDomain" content=settings.compilesUserContentDomain)
 meta(name="ol-compilesUserContentDomain" content=settings.compilesUserContentDomain)
-//- Set base path for Ace scripts loaded on demand/workers and don't use cdn
-meta(name="ol-aceBasePath" content="/js/" + lib('ace'))
 //- enable doc hash checking for all projects
 //- enable doc hash checking for all projects
 //- used in public/js/libs/sharejs.js
 //- used in public/js/libs/sharejs.js
 meta(name="ol-useShareJsHash" data-type="boolean" content=true)
 meta(name="ol-useShareJsHash" data-type="boolean" content=true)
 meta(name="ol-wsRetryHandshake" data-type="json" content=settings.wsRetryHandshake)
 meta(name="ol-wsRetryHandshake" data-type="json" content=settings.wsRetryHandshake)
 meta(name="ol-pdfjsVariant" content=pdfjsVariant)
 meta(name="ol-pdfjsVariant" content=pdfjsVariant)
 meta(name="ol-debugPdfDetach" data-type="boolean" content=debugPdfDetach)
 meta(name="ol-debugPdfDetach" data-type="boolean" content=debugPdfDetach)
-meta(name="ol-showLegacySourceEditor", data-type="boolean" content=showLegacySourceEditor)
-meta(name="ol-showSourceToolbar", data-type="boolean" content=showSourceToolbar)
 meta(name="ol-showSymbolPalette" data-type="boolean" content=showSymbolPalette)
 meta(name="ol-showSymbolPalette" data-type="boolean" content=showSymbolPalette)
 meta(name="ol-symbolPaletteAvailable" data-type="boolean" content=symbolPaletteAvailable)
 meta(name="ol-symbolPaletteAvailable" data-type="boolean" content=symbolPaletteAvailable)
 meta(name="ol-detachRole" data-type="string" content=detachRole)
 meta(name="ol-detachRole" data-type="string" content=detachRole)
@@ -34,7 +30,6 @@ meta(name="ol-showUpgradePrompt" data-type="boolean" content=showUpgradePrompt)
 meta(name="ol-useOpenTelemetry" data-type="boolean" content=useOpenTelemetry)
 meta(name="ol-useOpenTelemetry" data-type="boolean" content=useOpenTelemetry)
 meta(name="ol-showSupport", data-type="boolean" content=showSupport)
 meta(name="ol-showSupport", data-type="boolean" content=showSupport)
 meta(name="ol-showTemplatesServerPro", data-type="boolean" content=showTemplatesServerPro)
 meta(name="ol-showTemplatesServerPro", data-type="boolean" content=showTemplatesServerPro)
-meta(name="ol-showCM6SwitchAwaySurvey", data-type="boolean" content=showCM6SwitchAwaySurvey)
 meta(name="ol-showPersonalAccessToken", data-type="boolean" content=showPersonalAccessToken)
 meta(name="ol-showPersonalAccessToken", data-type="boolean" content=showPersonalAccessToken)
 meta(name="ol-optionalPersonalAccessToken", data-type="boolean" content=optionalPersonalAccessToken)
 meta(name="ol-optionalPersonalAccessToken", data-type="boolean" content=optionalPersonalAccessToken)
 meta(name="ol-isReviewPanelReact", data-type="boolean" content=isReviewPanelReact)
 meta(name="ol-isReviewPanelReact", data-type="boolean" content=isReviewPanelReact)

+ 1 - 1
services/web/app/views/project/editor/review-panel.pug

@@ -1,5 +1,5 @@
 #review-panel(
 #review-panel(
-	ng-class="{ 'rp-collapsed-displaying-entry': reviewPanel.entryHover, 'rp-offset-widgets': editor.showVisual }"
+	ng-class="{ 'rp-collapsed-displaying-entry': reviewPanel.entryHover, 'rp-offset-widgets': true }"
 )
 )
 	.rp-in-editor-widgets
 	.rp-in-editor-widgets
 		a.rp-track-changes-indicator(
 		a.rp-track-changes-indicator(

+ 0 - 32
services/web/app/views/project/editor/source-editor.pug

@@ -1,32 +0,0 @@
-#editor(
-	ace-editor="editor",
-	ng-show="!!editor.sharejs_doc && !editor.opening && multiSelectedCount === 0 && !editor.error_state",
-	theme="settings.editorTheme",
-	keybindings="settings.mode",
-	font-size="settings.fontSize",
-	auto-complete="settings.autoComplete",
-	auto-pair-delimiters="settings.autoPairDelimiters",
-	spell-check="!anonymous",
-	spell-check-language="project.spellCheckLanguage"
-	highlights="onlineUserCursorHighlights[editor.open_doc_id]"
-	show-print-margin="false",
-	sharejs-doc="editor.sharejs_doc",
-	last-updated="editor.last_updated",
-	cursor-position="editor.cursorPosition",
-	goto-line="editor.gotoLine",
-	resize-on="layout:main:resize,layout:pdf:resize,layout:review:resize,review-panel:toggle,layout:flat-screen:toggle",
-	annotations="pdf.logEntryAnnotations[editor.open_doc_id]",
-	read-only="!permissions.write",
-	file-name="editor.open_doc_name",
-	on-ctrl-j="toggleReviewPanel",
-	on-ctrl-shift-c="addNewCommentFromKbdShortcut",
-	on-ctrl-shift-a="toggleTrackChangesFromKbdShortcut",
-	syntax-validation="settings.syntaxValidation",
-	review-panel="reviewPanel",
-	events-bridge="reviewPanelEventsBridge"
-	track-changes= "editor.trackChanges",
-	doc-id="editor.open_doc_id"
-	renderer-data="reviewPanel.rendererData"
-	font-family="settings.fontFamily"
-	line-height="settings.lineHeight"
-)

+ 0 - 43
services/web/app/views/project/editor/toolbar.pug

@@ -1,43 +0,0 @@
-.toolbar.toolbar-editor(ng-controller="EditorToolbarController")
-	.toggle-wrapper
-		editor-switch
-
-	div(
-		formatting-buttons
-		ng-cloak
-		ng-if="!editor.showVisual"
-		buttons="editorButtons"
-		opening="editor.opening"
-		resize-on="layout:main:resize,layout:pdf:resize,layout:review:resize,review-panel:toggle"
-		is-fullscreen-editor="ui.view == 'editor' && ui.pdfLayout == 'flat'"
-		class="formatting-buttons"
-	)
-	div(
-		formatting-buttons
-		ng-cloak
-		ng-if="editor.showVisual"
-		buttons="[]"
-		opening="editor.opening"
-		resize-on="layout:main:resize,layout:pdf:resize,layout:review:resize,review-panel:toggle"
-		is-fullscreen-editor="ui.view == 'editor' && ui.pdfLayout == 'flat'"
-		class="formatting-buttons"
-	)
-
-	.toolbar-pdf-right
-		switch-to-pdf-button()
-		detacher-synctex-control()
-		editor-compile-button()
-
-	script(type="text/ng-template", id="formattingButtonsTpl")
-		.formatting-buttons-wrapper
-			|  
-			button.btn.formatting-btn.formatting-btn--icon(
-				ng-repeat="button in shownButtons"
-				ng-click="button.handleClick()"
-				ng-class="{ active: button.active }",
-				aria-label="{{button.title}}"
-				tooltip="{{button.title}}"
-				tooltip-placement="bottom"
-				tooltip-append-to-body="true"
-			)
-				i(class="{{button.iconClass}}") {{button.iconText}}

+ 23 - 1
services/web/frontend/js/features/dictionary/ignored-words.ts

@@ -1,5 +1,27 @@
 import getMeta from '../../utils/meta'
 import getMeta from '../../utils/meta'
-import { IGNORED_MISSPELLINGS } from '../../ide/editor/directives/aceEditor/spell-check/IgnoredMisspellings'
+
+const IGNORED_MISSPELLINGS = [
+  'Overleaf',
+  'overleaf',
+  'ShareLaTeX',
+  'sharelatex',
+  'LaTeX',
+  'TeX',
+  'BibTeX',
+  'BibLaTeX',
+  'XeTeX',
+  'XeLaTeX',
+  'LuaTeX',
+  'LuaLaTeX',
+  'http',
+  'https',
+  'www',
+  'COVID',
+  'Lockdown',
+  'lockdown',
+  'Coronavirus',
+  'coronavirus',
+]
 
 
 export class IgnoredWords {
 export class IgnoredWords {
   public learnedWords!: Set<string>
   public learnedWords!: Set<string>

+ 0 - 3
services/web/frontend/js/features/editor-left-menu/components/help-show-hotkeys.tsx

@@ -3,12 +3,10 @@ import { useTranslation } from 'react-i18next'
 import * as eventTracking from '../../../infrastructure/event-tracking'
 import * as eventTracking from '../../../infrastructure/event-tracking'
 import { useProjectContext } from '../../../shared/context/project-context'
 import { useProjectContext } from '../../../shared/context/project-context'
 import HotkeysModal from '../../hotkeys-modal/components/hotkeys-modal'
 import HotkeysModal from '../../hotkeys-modal/components/hotkeys-modal'
-import useScopeValue from '../../../shared/hooks/use-scope-value'
 import LeftMenuButton from './left-menu-button'
 import LeftMenuButton from './left-menu-button'
 
 
 export default function HelpShowHotkeys() {
 export default function HelpShowHotkeys() {
   const [showModal, setShowModal] = useState(false)
   const [showModal, setShowModal] = useState(false)
-  const [newSourceEditor] = useScopeValue('editor.newSourceEditor')
   const { t } = useTranslation()
   const { t } = useTranslation()
   const { features } = useProjectContext()
   const { features } = useProjectContext()
   const isMac = /Mac/.test(window.navigator?.platform)
   const isMac = /Mac/.test(window.navigator?.platform)
@@ -34,7 +32,6 @@ export default function HelpShowHotkeys() {
         handleHide={() => setShowModal(false)}
         handleHide={() => setShowModal(false)}
         isMac={isMac}
         isMac={isMac}
         trackChangesVisible={features?.trackChangesVisible}
         trackChangesVisible={features?.trackChangesVisible}
-        newSourceEditor={newSourceEditor}
       />
       />
     </>
     </>
   )
   )

+ 2 - 9
services/web/frontend/js/features/hotkeys-modal/components/hotkeys-modal.jsx

@@ -10,17 +10,11 @@ export default function HotkeysModal({
   show,
   show,
   isMac = false,
   isMac = false,
   trackChangesVisible = false,
   trackChangesVisible = false,
-  newSourceEditor = false,
 }) {
 }) {
   const { t } = useTranslation()
   const { t } = useTranslation()
 
 
-  const goToLineSuffix = newSourceEditor ? 'Shift + L' : 'L'
   const ctrl = isMac ? 'Cmd' : 'Ctrl'
   const ctrl = isMac ? 'Cmd' : 'Ctrl'
 
 
-  const modalTitle = newSourceEditor
-    ? `${t('hotkeys')} (Source editor)`
-    : `${t('hotkeys')} (Legacy source editor)`
-
   return (
   return (
     <AccessibleModal
     <AccessibleModal
       bsSize="large"
       bsSize="large"
@@ -29,7 +23,7 @@ export default function HotkeysModal({
       animation={animation}
       animation={animation}
     >
     >
       <Modal.Header closeButton>
       <Modal.Header closeButton>
-        <Modal.Title>{modalTitle}</Modal.Title>
+        <Modal.Title>{t('hotkeys')} (Source editor)</Modal.Title>
       </Modal.Header>
       </Modal.Header>
 
 
       <Modal.Body className="hotkeys-modal">
       <Modal.Body className="hotkeys-modal">
@@ -77,7 +71,7 @@ export default function HotkeysModal({
           </Col>
           </Col>
           <Col xs={4}>
           <Col xs={4}>
             <Hotkey
             <Hotkey
-              combination={`${ctrl} + ${goToLineSuffix}`}
+              combination={`${ctrl} + Shift + L`}
               description={t('hotkey_go_to_line')}
               description={t('hotkey_go_to_line')}
             />
             />
           </Col>
           </Col>
@@ -211,7 +205,6 @@ HotkeysModal.propTypes = {
   show: PropTypes.bool.isRequired,
   show: PropTypes.bool.isRequired,
   handleHide: PropTypes.func.isRequired,
   handleHide: PropTypes.func.isRequired,
   trackChangesVisible: PropTypes.bool,
   trackChangesVisible: PropTypes.bool,
-  newSourceEditor: PropTypes.bool,
 }
 }
 
 
 function Hotkey({ combination, description }) {
 function Hotkey({ combination, description }) {

+ 0 - 60
services/web/frontend/js/features/outline/controllers/outline-controller.js

@@ -1,60 +0,0 @@
-import App from '../../../base'
-import OutlinePane from '../components/outline-pane'
-import { react2angular } from 'react2angular'
-import { rootContext } from '../../../shared/context/root-context'
-
-App.controller('OutlineController', [
-  '$scope',
-  'ide',
-  'eventTracking',
-  function ($scope, ide, eventTracking) {
-    $scope.isTexFile = false
-    $scope.outline = []
-    $scope.eventTracking = eventTracking
-
-    function shouldShowOutline() {
-      return !$scope.editor.newSourceEditor
-    }
-
-    $scope.show = shouldShowOutline()
-
-    $scope.$watch('editor.newSourceEditor', function () {
-      $scope.show = shouldShowOutline()
-    })
-
-    $scope.$on('outline-manager:outline-changed', onOutlineChange)
-
-    function onOutlineChange(e, outlineInfo) {
-      $scope.$applyAsync(() => {
-        $scope.isTexFile = outlineInfo.isTexFile
-        $scope.outline = outlineInfo.outline
-        $scope.highlightedLine = outlineInfo.highlightedLine
-      })
-    }
-
-    $scope.jumpToLine = (lineNo, syncToPdf) => {
-      ide.outlineManager.jumpToLine(lineNo, syncToPdf)
-      eventTracking.sendMB('outline-jump-to-line')
-    }
-
-    $scope.onToggle = isOpen => {
-      $scope.$applyAsync(() => {
-        $scope.$emit('outline-toggled', isOpen)
-      })
-    }
-  },
-])
-
-// Wrap React component as Angular component. Only needed for "top-level" component
-App.component(
-  'outlinePane',
-  react2angular(rootContext.use(OutlinePane), [
-    'outline',
-    'jumpToLine',
-    'highlightedLine',
-    'eventTracking',
-    'onToggle',
-    'isTexFile',
-    'show',
-  ])
-)

+ 0 - 128
services/web/frontend/js/features/outline/outline-manager.js

@@ -1,128 +0,0 @@
-import { isEqual, cloneDeep } from 'lodash'
-import './controllers/outline-controller'
-import { matchOutline, nestOutline } from './outline-parser'
-import isValidTeXFile from '../../main/is-valid-tex-file'
-
-class OutlineManager {
-  constructor(ide, scope) {
-    this.ide = ide
-    this.scope = scope
-    this.shareJsDoc = null
-    this.isTexFile = false
-    this.flatOutline = []
-    this.previousFlatOutline = []
-    this.outline = []
-    this.highlightedLine = null
-    this.ignoreNextScroll = false
-    this.ignoreNextCursorUpdate = false
-
-    scope.$watch('editor.newSourceEditor', (now, before) => {
-      if (before && !now) {
-        this.updateOutline()
-        this.broadcastChangeEvent()
-      }
-    })
-
-    scope.$on('doc:after-opened', (ev, { isNewDoc }) => {
-      if (isNewDoc) {
-        // if a new doc is opened a cursor updates will be triggered before the
-        // content is loaded. We have to ignore it or the outline highlight
-        // will be incorrect. This doesn't happen when `doc:after-opened` is
-        // fired without a new doc opened.
-        this.ignoreNextCursorUpdate = true
-      }
-      // always ignore the next scroll update so the cursor update takes
-      // precedence
-      this.ignoreNextScroll = true
-      this.shareJsDoc = scope.editor.sharejs_doc
-      this.isTexFile = isValidTeXFile(scope.editor.open_doc_name)
-      this.updateOutline()
-      this.broadcastChangeEvent()
-    })
-
-    scope.$on('doc:changed', () => {
-      this.updateOutline()
-      this.broadcastChangeEvent()
-    })
-
-    scope.$on('file-view:file-opened', () => {
-      this.isTexFile = false
-      this.updateOutline()
-      this.broadcastChangeEvent()
-    })
-
-    scope.$on('cursor:editor:update', (event, cursorPosition) => {
-      if (this.ignoreNextCursorUpdate) {
-        this.ignoreNextCursorUpdate = false
-        return
-      }
-      this.updateHighlightedLine(cursorPosition.row + 1)
-      this.broadcastChangeEvent()
-    })
-
-    scope.$on('scroll:editor:update', (event, middleVisibleRow) => {
-      if (this.ignoreNextScroll) {
-        this.ignoreNextScroll = false
-        return
-      }
-
-      this.updateHighlightedLine(middleVisibleRow + 1)
-    })
-  }
-
-  shouldShowOutline() {
-    return !this.scope.editor.newSourceEditor
-  }
-
-  updateOutline() {
-    // Disable if using CM6
-    if (!this.shouldShowOutline()) {
-      return
-    }
-    if (this.isTexFile) {
-      const content = this.ide.editorManager.getCurrentDocValue()
-      if (content) {
-        this.flatOutline = matchOutline(content)
-        if (!isEqual(this.flatOutline, this.previousFlatOutline)) {
-          this.previousFlatOutline = cloneDeep(this.flatOutline)
-          this.outline = nestOutline(this.flatOutline)
-        }
-        return
-      }
-    }
-    this.previousFlatOutline = []
-    this.outline = []
-  }
-
-  // set highlightedLine to the closest outline line above the editorLine
-  updateHighlightedLine(editorLine) {
-    let closestOutlineLine = null
-    for (let lineId = 0; lineId < this.flatOutline.length; lineId++) {
-      const outline = this.flatOutline[lineId]
-      if (editorLine < outline.line) break // editorLine is above
-      closestOutlineLine = outline.line
-    }
-    if (closestOutlineLine === this.highlightedLine) return
-    this.highlightedLine = closestOutlineLine
-    this.broadcastChangeEvent()
-  }
-
-  jumpToLine(line, syncToPdf) {
-    this.ignoreNextScroll = true
-    this.ide.editorManager.jumpToLine({ gotoLine: line, syncToPdf })
-  }
-
-  broadcastChangeEvent() {
-    // Disable if using CM6
-    if (!this.shouldShowOutline()) {
-      return
-    }
-    this.scope.$broadcast('outline-manager:outline-changed', {
-      isTexFile: this.isTexFile,
-      outline: this.outline,
-      highlightedLine: this.highlightedLine,
-    })
-  }
-}
-
-export default OutlineManager

+ 0 - 133
services/web/frontend/js/features/outline/outline-parser.js

@@ -1,133 +0,0 @@
-const COMMAND_LEVELS = {
-  book: 10,
-  part: 20,
-  addpart: 20,
-  chapter: 30,
-  addchap: 30,
-  section: 40,
-  addsec: 40,
-  subsection: 50,
-  subsubsection: 60,
-  paragraph: 70,
-  subparagraph: 80,
-}
-
-/*
- *
- * RegExp matcher parts:
- *
- * REGEX_START: begining of line, any number of spaces, double \ (required)
- * REGEX_COMMAND: any of the listed commands (required)
- * REGEX_SPACING: spaces and * between groups (optional)
- * REGEX_SHORT_TITLE: a text between square brackets (optional)
- * REGEX_TITLE: a text between curly brackets (required)
- *
- */
-const REGEX_START = '^\\s*\\\\'
-const REGEX_COMMAND = `(${Object.keys(COMMAND_LEVELS).join('|')})`
-const REGEX_SPACING = '\\s?\\*?\\s?'
-const REGEX_SHORT_TITLE = '(\\[([^\\]]+)\\])?'
-const REGEX_TITLE = '{(.*)}'
-const MATCHER = new RegExp(
-  `${REGEX_START}${REGEX_COMMAND}${REGEX_SPACING}${REGEX_SHORT_TITLE}${REGEX_SPACING}${REGEX_TITLE}`
-)
-
-function matchOutline(content) {
-  const lines = content.split('\n')
-  const flatOutline = matchOutlineFromLines(lines)
-  return flatOutline
-}
-
-function matchOutlineFromLines(lines) {
-  const flatOutline = []
-  lines.forEach((line, lineId) => {
-    const match = line.match(MATCHER)
-    if (!match) return
-    const [, command, , shortTitle, title] = match
-
-    flatOutline.push({
-      line: lineId + 1,
-      title: matchDisplayTitle(shortTitle || title),
-      level: COMMAND_LEVELS[command],
-    })
-  })
-  return flatOutline
-}
-
-const DISPLAY_TITLE_REGEX = /([^\\]*)\\([^{]+){([^}]+)}(.*)/
-const END_OF_TITLE_REGEX = /^([^{}]*?({[^{}]*?}[^{}]*?)*)}/
-/*
- * Attempt to improve the display of the outline title for titles with commands.
- * Either skip the command (for labels) or display the command's content instead
- * of the entire command.
- *
- * e.g. "Label \\label{foo} between" => "Label  between"
- * e.g. "TT \\texttt{Bar}" => "TT Bar"
- *
- */
-function matchDisplayTitle(title) {
-  const closingBracketPosition = title.indexOf('}')
-
-  if (closingBracketPosition < 0) {
-    // simple title (no commands)
-    return title
-  }
-
-  // if there is anything outside the title def on the line, remove it
-  // before proceeding
-  const titleOnlyMatch = title.match(END_OF_TITLE_REGEX)
-  if (titleOnlyMatch) {
-    title = titleOnlyMatch[1]
-  }
-
-  const titleMatch = title.match(DISPLAY_TITLE_REGEX)
-  if (!titleMatch) {
-    // no contained commands; strip everything after the first closing bracket
-    return title.substring(0, closingBracketPosition)
-  }
-
-  const [, textBefore, command, commandContent, textAfter] = titleMatch
-  if (command === 'label') {
-    // label: don't display them at all
-    title = `${textBefore}${textAfter}`
-  } else {
-    // display the content of the command. Works well for formatting commands
-    title = `${textBefore}${commandContent}${textAfter}`
-  }
-
-  return title
-}
-
-function nestOutline(flatOutline) {
-  const parentOutlines = {}
-  const nestedOutlines = []
-  flatOutline.forEach(outline => {
-    const parentOutlineLevels = Object.keys(parentOutlines)
-
-    // find the nearest parent outline
-    const nearestParentLevel = parentOutlineLevels
-      .reverse()
-      .find(level => level < outline.level)
-    const parentOutline = parentOutlines[nearestParentLevel]
-    if (!parentOutline) {
-      // top level
-      nestedOutlines.push(outline)
-    } else if (!parentOutline.children) {
-      // first outline in this node
-      parentOutline.children = [outline]
-    } else {
-      // push outline to node
-      parentOutline.children.push(outline)
-    }
-
-    // store the outline as new parent at its level and forget lower-level
-    // outlines (if any) as they shouldn't get any children anymore
-    parentOutlines[outline.level] = outline
-    parentOutlineLevels
-      .filter(level => level > outline.level)
-      .forEach(level => delete parentOutlines[level])
-  })
-  return nestedOutlines
-}
-
-export { matchOutline, matchOutlineFromLines, nestOutline }

+ 0 - 93
services/web/frontend/js/features/source-editor/components/cm6-switch-away-survey.tsx

@@ -1,93 +0,0 @@
-import { useCallback, useEffect, useState } from 'react'
-import { Button } from 'react-bootstrap'
-import useScopeValue from '../../../shared/hooks/use-scope-value'
-import {
-  hasSeenCM6SwitchAwaySurvey,
-  setHasSeenCM6SwitchAwaySurvey,
-} from '../utils/switch-away-survey'
-import { sendMB } from '../../../infrastructure/event-tracking'
-
-type CM6SwitchAwaySurveyState = 'disabled' | 'enabled' | 'shown'
-
-export default function CM6SwitchAwaySurvey() {
-  const [state, setState] = useState<CM6SwitchAwaySurveyState>('disabled')
-  const [newSourceEditor] = useScopeValue('editor.newSourceEditor')
-
-  useEffect(() => {
-    // If the user has previously seen any switch-away survey, then don't show
-    // the current one
-    if (hasSeenCM6SwitchAwaySurvey()) return
-
-    if (!newSourceEditor) {
-      setState('enabled')
-    } else {
-      setState('disabled')
-    }
-  }, [newSourceEditor])
-
-  useEffect(() => {
-    const handleKeyDown = () => {
-      const TIME_FOR_SURVEY_TO_APPEAR = 3000
-
-      setTimeout(() => {
-        if (state === 'enabled') {
-          setState('shown')
-          setHasSeenCM6SwitchAwaySurvey()
-        }
-      }, TIME_FOR_SURVEY_TO_APPEAR)
-    }
-
-    // can't access the ace editor directly, so add the keydown event
-    // to window
-    window?.addEventListener('keydown', handleKeyDown, { once: true })
-
-    return () => window?.removeEventListener('keydown', handleKeyDown)
-  }, [state])
-
-  const handleClose = useCallback(() => {
-    setState('disabled')
-  }, [])
-
-  const handleFollowLink = useCallback(() => {
-    sendMB('cm6-switch-away-survey')
-    setState('disabled')
-  }, [])
-
-  if (state !== 'shown') {
-    return null
-  }
-
-  return (
-    <div className="alert alert-success cm6-switch-away-survey" role="alert">
-      <Button
-        className="close"
-        data-dismiss="alert"
-        aria-label="Close"
-        onClick={handleClose}
-        bsStyle={null}
-      >
-        <span aria-hidden="true">&times;</span>
-      </Button>
-      <div className="warning-content">
-        <div>
-          <div className="warning-text">
-            We noticed that you're still using the{' '}
-            <strong>Source (legacy)</strong> editor.
-          </div>
-          <div className="warning-text">Could you let us know why?</div>
-        </div>
-        <div style={{ display: 'inline-flex' }}>
-          <a
-            href="https://forms.gle/Ygv8gLZ4N8LepQj56"
-            className="btn btn-sm btn-info"
-            target="_blank"
-            rel="noreferrer"
-            onClick={handleFollowLink}
-          >
-            Take survey
-          </a>
-        </div>
-      </div>
-    </div>
-  )
-}

+ 8 - 11
services/web/frontend/js/features/source-editor/components/codemirror-toolbar.tsx

@@ -17,7 +17,6 @@ import EditorSwitch from './editor-switch'
 import SwitchToPDFButton from './switch-to-pdf-button'
 import SwitchToPDFButton from './switch-to-pdf-button'
 import { DetacherSynctexControl } from '../../pdf-preview/components/detach-synctex-control'
 import { DetacherSynctexControl } from '../../pdf-preview/components/detach-synctex-control'
 import DetachCompileButtonWrapper from '../../pdf-preview/components/detach-compile-button-wrapper'
 import DetachCompileButtonWrapper from '../../pdf-preview/components/detach-compile-button-wrapper'
-import getMeta from '../../../utils/meta'
 import { isVisual } from '../extensions/visual/visual'
 import { isVisual } from '../extensions/visual/visual'
 import { language } from '@codemirror/language'
 import { language } from '@codemirror/language'
 import { minimumListDepthForSelection } from '../utils/tree-operations/ancestors'
 import { minimumListDepthForSelection } from '../utils/tree-operations/ancestors'
@@ -34,8 +33,6 @@ export const CodeMirrorToolbar = () => {
 }
 }
 
 
 const Toolbar = memo(function Toolbar() {
 const Toolbar = memo(function Toolbar() {
-  const showSourceToolbar: boolean = getMeta('ol-showSourceToolbar')
-
   const state = useCodeMirrorStateContext()
   const state = useCodeMirrorStateContext()
   const view = useCodeMirrorViewContext()
   const view = useCodeMirrorViewContext()
 
 
@@ -117,7 +114,7 @@ const Toolbar = memo(function Toolbar() {
 
 
   return (
   return (
     <div className="ol-cm-toolbar toolbar-editor" ref={elementRef}>
     <div className="ol-cm-toolbar toolbar-editor" ref={elementRef}>
-      {showSourceToolbar && <EditorSwitch />}
+      <EditorSwitch />
       {showActions && (
       {showActions && (
         <ToolbarItems
         <ToolbarItems
           state={state}
           state={state}
@@ -126,6 +123,7 @@ const Toolbar = memo(function Toolbar() {
           listDepth={listDepth}
           listDepth={listDepth}
         />
         />
       )}
       )}
+
       <div className="ol-cm-toolbar-button-group ol-cm-toolbar-stretch">
       <div className="ol-cm-toolbar-button-group ol-cm-toolbar-stretch">
         {showActions && (
         {showActions && (
           <ToolbarOverflow
           <ToolbarOverflow
@@ -143,8 +141,10 @@ const Toolbar = memo(function Toolbar() {
             />
             />
           </ToolbarOverflow>
           </ToolbarOverflow>
         )}
         )}
+
         <div className="formatting-buttons-wrapper" />
         <div className="formatting-buttons-wrapper" />
       </div>
       </div>
+
       <div className="ol-cm-toolbar-button-group ol-cm-toolbar-end">
       <div className="ol-cm-toolbar-button-group ol-cm-toolbar-end">
         <ToolbarButton
         <ToolbarButton
           id="toolbar-toggle-search"
           id="toolbar-toggle-search"
@@ -153,13 +153,10 @@ const Toolbar = memo(function Toolbar() {
           active={searchPanelOpen(state)}
           active={searchPanelOpen(state)}
           icon="search"
           icon="search"
         />
         />
-        {showSourceToolbar && (
-          <>
-            <SwitchToPDFButton />
-            <DetacherSynctexControl />
-            <DetachCompileButtonWrapper />
-          </>
-        )}
+
+        <SwitchToPDFButton />
+        <DetacherSynctexControl />
+        <DetachCompileButtonWrapper />
       </div>
       </div>
       <div className="ol-cm-toolbar-button-group hidden">
       <div className="ol-cm-toolbar-button-group hidden">
         <ToolbarButton
         <ToolbarButton

+ 0 - 172
services/web/frontend/js/features/source-editor/components/editor-switch-legacy.tsx

@@ -1,172 +0,0 @@
-import { ChangeEvent, FC, memo, useCallback } from 'react'
-import useScopeValue from '../../../shared/hooks/use-scope-value'
-import Tooltip from '../../../shared/components/tooltip'
-import { sendMB } from '../../../infrastructure/event-tracking'
-import getMeta from '../../../utils/meta'
-import isValidTeXFile from '../../../main/is-valid-tex-file'
-import { useTranslation } from 'react-i18next'
-
-function Badge() {
-  const content = (
-    <>
-      Overleaf has upgraded the source editor.
-      <br />
-      You can still use the old editor by selecting "Source (legacy)".
-      <br />
-      <br />
-      Click to learn more and give feedback
-    </>
-  )
-
-  return (
-    <Tooltip
-      id="editor-switch"
-      description={content}
-      overlayProps={{
-        placement: 'bottom',
-        delayHide: 100,
-      }}
-      tooltipProps={{ className: 'tooltip-wide' }}
-    >
-      <a
-        href="https://forms.gle/GmSs6odZRKRp3VX98"
-        target="_blank"
-        rel="noopener noreferrer"
-        className="info-badge"
-      >
-        <span className="sr-only">{content}</span>
-      </a>
-    </Tooltip>
-  )
-}
-
-const showLegacySourceEditor: boolean = getMeta('ol-showLegacySourceEditor')
-
-function EditorSwitch() {
-  const { t } = useTranslation()
-  const [newSourceEditor, setNewSourceEditor] = useScopeValue(
-    'editor.newSourceEditor'
-  )
-  const [visual, setVisual] = useScopeValue('editor.showVisual')
-
-  const [docName] = useScopeValue('editor.open_doc_name')
-  const richTextAvailable = isValidTeXFile(docName)
-  // TODO: rename this after legacy & toolbar split tests are complete
-  const richTextOrVisual = richTextAvailable && visual
-
-  const handleChange = useCallback(
-    event => {
-      const editorType = event.target.value
-
-      switch (editorType) {
-        case 'ace':
-          setVisual(false)
-          setNewSourceEditor(false)
-          break
-
-        case 'cm6':
-          setVisual(false)
-          setNewSourceEditor(true)
-          break
-
-        case 'rich-text':
-          setVisual(true)
-          setNewSourceEditor(true)
-
-          break
-      }
-
-      sendMB('editor-switch-change', { editorType })
-    },
-    [setVisual, setNewSourceEditor]
-  )
-
-  return (
-    <div className="editor-toggle-switch">
-      {showLegacySourceEditor ? <Badge /> : null}
-
-      <fieldset className="toggle-switch">
-        <legend className="sr-only">Editor mode.</legend>
-
-        <input
-          type="radio"
-          name="editor"
-          value="cm6"
-          id="editor-switch-cm6"
-          className="toggle-switch-input"
-          checked={!richTextOrVisual && !!newSourceEditor}
-          onChange={handleChange}
-        />
-        <label htmlFor="editor-switch-cm6" className="toggle-switch-label">
-          <span>{t('code_editor')}</span>
-        </label>
-
-        {showLegacySourceEditor ? (
-          <>
-            <input
-              type="radio"
-              name="editor"
-              value="ace"
-              id="editor-switch-ace"
-              className="toggle-switch-input"
-              checked={!richTextOrVisual && !newSourceEditor}
-              onChange={handleChange}
-            />
-            <label htmlFor="editor-switch-ace" className="toggle-switch-label">
-              <span>Source (legacy)</span>
-            </label>
-          </>
-        ) : null}
-
-        <RichTextToggle
-          checked={!!richTextOrVisual}
-          disabled={!richTextAvailable}
-          handleChange={handleChange}
-        />
-      </fieldset>
-    </div>
-  )
-}
-
-const RichTextToggle: FC<{
-  checked: boolean
-  disabled: boolean
-  handleChange: (event: ChangeEvent<HTMLInputElement>) => void
-}> = ({ checked, disabled, handleChange }) => {
-  const { t } = useTranslation()
-
-  const toggle = (
-    <span>
-      <input
-        type="radio"
-        name="editor"
-        value="rich-text"
-        id="editor-switch-rich-text"
-        className="toggle-switch-input"
-        checked={checked}
-        onChange={handleChange}
-        disabled={disabled}
-      />
-      <label htmlFor="editor-switch-rich-text" className="toggle-switch-label">
-        <span>{t('visual_editor')}</span>
-      </label>
-    </span>
-  )
-
-  if (disabled) {
-    return (
-      <Tooltip
-        description={t('visual_editor_is_only_available_for_tex_files')}
-        id="rich-text-toggle-tooltip"
-        overlayProps={{ placement: 'bottom' }}
-        tooltipProps={{ className: 'tooltip-wide' }}
-      >
-        {toggle}
-      </Tooltip>
-    )
-  }
-
-  return toggle
-}
-
-export default memo(EditorSwitch)

+ 5 - 17
services/web/frontend/js/features/source-editor/components/editor-switch.tsx

@@ -9,40 +9,28 @@ import { FeedbackBadge } from '@/shared/components/feedback-badge'
 
 
 function EditorSwitch() {
 function EditorSwitch() {
   const { t } = useTranslation()
   const { t } = useTranslation()
-  const [newSourceEditor, setNewSourceEditor] = useScopeValue(
-    'editor.newSourceEditor'
-  )
   const [visual, setVisual] = useScopeValue('editor.showVisual')
   const [visual, setVisual] = useScopeValue('editor.showVisual')
-
   const [docName] = useScopeValue('editor.open_doc_name')
   const [docName] = useScopeValue('editor.open_doc_name')
+
   const richTextAvailable = isValidTeXFile(docName)
   const richTextAvailable = isValidTeXFile(docName)
-  // TODO: rename this after legacy & toolbar split tests are complete
-  const richTextOrVisual = richTextAvailable && visual
 
 
   const handleChange = useCallback(
   const handleChange = useCallback(
     event => {
     event => {
       const editorType = event.target.value
       const editorType = event.target.value
 
 
       switch (editorType) {
       switch (editorType) {
-        case 'ace':
-          setVisual(false)
-          setNewSourceEditor(false)
-          break
-
         case 'cm6':
         case 'cm6':
           setVisual(false)
           setVisual(false)
-          setNewSourceEditor(true)
           break
           break
 
 
         case 'rich-text':
         case 'rich-text':
           setVisual(true)
           setVisual(true)
-          setNewSourceEditor(true)
           break
           break
       }
       }
 
 
       sendMB('editor-switch-change', { editorType })
       sendMB('editor-switch-change', { editorType })
     },
     },
-    [setVisual, setNewSourceEditor]
+    [setVisual]
   )
   )
 
 
   return (
   return (
@@ -56,7 +44,7 @@ function EditorSwitch() {
           value="cm6"
           value="cm6"
           id="editor-switch-cm6"
           id="editor-switch-cm6"
           className="toggle-switch-input"
           className="toggle-switch-input"
-          checked={!richTextOrVisual && !!newSourceEditor}
+          checked={!richTextAvailable || !visual}
           onChange={handleChange}
           onChange={handleChange}
         />
         />
         <label htmlFor="editor-switch-cm6" className="toggle-switch-label">
         <label htmlFor="editor-switch-cm6" className="toggle-switch-label">
@@ -64,13 +52,13 @@ function EditorSwitch() {
         </label>
         </label>
 
 
         <RichTextToggle
         <RichTextToggle
-          checked={!!richTextOrVisual}
+          checked={richTextAvailable && visual}
           disabled={!richTextAvailable}
           disabled={!richTextAvailable}
           handleChange={handleChange}
           handleChange={handleChange}
         />
         />
       </fieldset>
       </fieldset>
 
 
-      {!!richTextOrVisual && (
+      {richTextAvailable && visual && (
         <FeedbackBadge
         <FeedbackBadge
           id="visual-editor-feedback"
           id="visual-editor-feedback"
           url="https://forms.gle/AUqHmKNiEH3DRniPA"
           url="https://forms.gle/AUqHmKNiEH3DRniPA"

+ 0 - 81
services/web/frontend/js/features/source-editor/components/legacy-editor-warning.tsx

@@ -1,81 +0,0 @@
-import { memo, useCallback, useEffect, useState } from 'react'
-import { Button } from 'react-bootstrap'
-import customLocalStorage from '../../../infrastructure/local-storage'
-import useScopeValue from '../../../shared/hooks/use-scope-value'
-import * as eventTracking from '../../../infrastructure/event-tracking'
-
-export const LegacyEditorWarning = memo(function LegacyEditorWarning({
-  delay,
-}: {
-  delay: number
-}) {
-  const [show, setShow] = useState(false)
-  const [newSourceEditor] = useScopeValue('editor.newSourceEditor')
-  const hasDismissedLegacyEditor = customLocalStorage.getItem(
-    'editor.has_dismissed_legacy_editor_warning'
-  )
-
-  useEffect(() => {
-    const showLegacyEditor =
-      !hasDismissedLegacyEditor && newSourceEditor === false
-
-    let timeoutId: number | undefined
-
-    if (showLegacyEditor) {
-      timeoutId = window.setTimeout(() => {
-        eventTracking.sendMB('legacy-editor-warning-prompt')
-        setShow(true)
-      }, delay)
-    }
-
-    return () => {
-      window.clearTimeout(timeoutId)
-    }
-  }, [hasDismissedLegacyEditor, newSourceEditor, delay])
-
-  const handleClose = useCallback(() => {
-    setShow(false)
-    customLocalStorage.setItem(
-      'editor.has_dismissed_legacy_editor_warning',
-      true
-    )
-    eventTracking.sendMB('legacy-editor-warning-dismiss')
-  }, [])
-
-  const handleClick = useCallback(() => {
-    eventTracking.sendMB('legacy-editor-warning-link-click')
-  }, [])
-
-  if (!show) {
-    return null
-  }
-
-  return (
-    <div className="alert alert-info legacy-editor-warning" role="alert">
-      <Button
-        className="close"
-        data-dismiss="alert"
-        aria-label="Close"
-        onClick={handleClose}
-        bsStyle={null}
-      >
-        <span aria-hidden="true">&times;</span>
-      </Button>
-      <div className="warning-content">
-        <div>We're retiring our Source (legacy) editor in late May 2023.</div>
-        <div>
-          <a
-            className="warning-link"
-            href="https://www.overleaf.com/blog/were-retiring-our-legacy-source-editor"
-            target="_blank"
-            rel="noreferrer"
-            onClick={handleClick}
-          >
-            Read the blog post
-          </a>{' '}
-          to learn more and find out how to report any problems.
-        </div>
-      </div>
-    </div>
-  )
-})

+ 0 - 9
services/web/frontend/js/features/source-editor/controllers/cm6-switch-away-survey-controller.js

@@ -1,9 +0,0 @@
-import App from '../../../base'
-import { react2angular } from 'react2angular'
-import { rootContext } from '../../../shared/context/root-context'
-import CM6SwitchAwaySurvey from '../components/cm6-switch-away-survey'
-
-App.component(
-  'cm6SwitchAwaySurvey',
-  react2angular(rootContext.use(CM6SwitchAwaySurvey))
-)

+ 0 - 6
services/web/frontend/js/features/source-editor/controllers/editor-switch-controller.js

@@ -1,6 +0,0 @@
-import App from '../../../base'
-import { react2angular } from 'react2angular'
-import EditorSwitch from '../components/editor-switch-legacy'
-import { rootContext } from '../../../shared/context/root-context'
-
-App.component('editorSwitch', react2angular(rootContext.use(EditorSwitch)))

+ 0 - 9
services/web/frontend/js/features/source-editor/controllers/legacy-editor-warning-controller.js

@@ -1,9 +0,0 @@
-import App from '../../../base'
-import { react2angular } from 'react2angular'
-import { rootContext } from '../../../shared/context/root-context'
-import { LegacyEditorWarning } from '../components/legacy-editor-warning'
-
-App.component(
-  'legacyEditorWarning',
-  react2angular(rootContext.use(LegacyEditorWarning), ['delay'])
-)

+ 1 - 2
services/web/frontend/js/features/source-editor/extensions/changes/change-manager.ts

@@ -15,8 +15,7 @@ import getMeta from '../../../../utils/meta'
 
 
 // If a toolbar row sits alongside the review panel, the review panel entries need to be shifted down by 32px.
 // If a toolbar row sits alongside the review panel, the review panel entries need to be shifted down by 32px.
 // Once the review panel is always inside the editor, this offset can be removed.
 // Once the review panel is always inside the editor, this offset can be removed.
-const offsetTop =
-  getMeta('ol-showSourceToolbar') && !getMeta('ol-isReviewPanelReact') ? 32 : 0
+const offsetTop = getMeta('ol-isReviewPanelReact') ? 0 : 32
 
 
 // With less than this number of entries, don't bother culling to avoid
 // With less than this number of entries, don't bother culling to avoid
 // little UI jumps when scrolling.
 // little UI jumps when scrolling.

+ 0 - 11
services/web/frontend/js/features/source-editor/utils/switch-away-survey.ts

@@ -1,11 +0,0 @@
-import localStorage from '../../../infrastructure/local-storage'
-
-const surveyOneKey = 'editor.has_seen_cm6_switch_away_survey'
-
-export function setHasSeenCM6SwitchAwaySurvey() {
-  localStorage.setItem(surveyOneKey, true)
-}
-
-export function hasSeenCM6SwitchAwaySurvey() {
-  return !!localStorage.getItem(surveyOneKey)
-}

+ 0 - 68
services/web/frontend/js/ide.js

@@ -28,8 +28,6 @@ import BinaryFilesManager from './ide/binary-files/BinaryFilesManager'
 import ReferencesManager from './ide/references/ReferencesManager'
 import ReferencesManager from './ide/references/ReferencesManager'
 import MetadataManager from './ide/metadata/MetadataManager'
 import MetadataManager from './ide/metadata/MetadataManager'
 import './ide/review-panel/ReviewPanelManager'
 import './ide/review-panel/ReviewPanelManager'
-import OutlineManager from './features/outline/outline-manager'
-import SafariScrollPatcher from './ide/SafariScrollPatcher'
 import './ide/cobranding/CobrandingDataService'
 import './ide/cobranding/CobrandingDataService'
 import './ide/chat/index'
 import './ide/chat/index'
 import './ide/file-view/index'
 import './ide/file-view/index'
@@ -59,14 +57,10 @@ import './shared/context/controllers/root-context-controller'
 import './features/editor-navigation-toolbar/controllers/editor-navigation-toolbar-controller'
 import './features/editor-navigation-toolbar/controllers/editor-navigation-toolbar-controller'
 import './features/pdf-preview/controllers/pdf-preview-controller'
 import './features/pdf-preview/controllers/pdf-preview-controller'
 import './features/share-project-modal/controllers/react-share-project-modal-controller'
 import './features/share-project-modal/controllers/react-share-project-modal-controller'
-import './features/source-editor/controllers/editor-switch-controller'
-import './features/source-editor/controllers/cm6-switch-away-survey-controller'
-import './features/source-editor/controllers/legacy-editor-warning-controller'
 import './features/history/controllers/history-controller'
 import './features/history/controllers/history-controller'
 import './features/editor-left-menu/controllers/editor-left-menu-controller'
 import './features/editor-left-menu/controllers/editor-left-menu-controller'
 import { cleanupServiceWorker } from './utils/service-worker-cleanup'
 import { cleanupServiceWorker } from './utils/service-worker-cleanup'
 import { reportCM6Perf } from './infrastructure/cm6-performance'
 import { reportCM6Perf } from './infrastructure/cm6-performance'
-import { reportAcePerf } from './ide/editor/ace-performance'
 import { debugConsole } from '@/utils/debugging'
 import { debugConsole } from '@/utils/debugging'
 
 
 App.controller('IdeController', [
 App.controller('IdeController', [
@@ -215,7 +209,6 @@ App.controller('IdeController', [
     ide.permissionsManager = new PermissionsManager(ide, $scope)
     ide.permissionsManager = new PermissionsManager(ide, $scope)
     ide.binaryFilesManager = new BinaryFilesManager(ide, $scope)
     ide.binaryFilesManager = new BinaryFilesManager(ide, $scope)
     ide.metadataManager = new MetadataManager(ide, $scope, metadata)
     ide.metadataManager = new MetadataManager(ide, $scope, metadata)
-    ide.outlineManager = new OutlineManager(ide, $scope)
 
 
     let inited = false
     let inited = false
     $scope.$on('project:joined', function () {
     $scope.$on('project:joined', function () {
@@ -301,32 +294,6 @@ If the project has been renamed please look in your project list for a new proje
               }
               }
             }
             }
           }
           }
-        } else if (editorType === 'ace') {
-          const acePerfData = reportAcePerf()
-
-          if (acePerfData.numberOfEntries > 0) {
-            const perfProps = [
-              'NumberOfEntries',
-              'MeanKeypressPaint',
-              'Grammarly',
-              'SessionLength',
-              'Memory',
-              'Lags',
-              'NonLags',
-              'LongestLag',
-              'MeanLagsPerMeasure',
-              'MeanKeypressesPerMeasure',
-              'Release',
-            ]
-
-            for (const prop of perfProps) {
-              const perfValue =
-                acePerfData[prop.charAt(0).toLowerCase() + prop.slice(1)]
-              if (perfValue !== null) {
-                segmentation['acePerf' + prop] = perfValue
-              }
-            }
-          }
         }
         }
 
 
         return segmentation
         return segmentation
@@ -374,8 +341,6 @@ If the project has been renamed please look in your project list for a new proje
 
 
     ide.localStorage = localStorage
     ide.localStorage = localStorage
 
 
-    ide.browserIsSafari = false
-
     $scope.switchToFlatLayout = function (view) {
     $scope.switchToFlatLayout = function (view) {
       $scope.ui.pdfLayout = 'flat'
       $scope.ui.pdfLayout = 'flat'
       $scope.ui.view = view
       $scope.ui.view = view
@@ -418,39 +383,6 @@ If the project has been renamed please look in your project list for a new proje
       // unused?
       // unused?
     }
     }
 
 
-    try {
-      ;({ userAgent } = navigator)
-      ide.browserIsSafari =
-        userAgent &&
-        /.*Safari\/.*/.test(userAgent) &&
-        !/.*Chrome\/.*/.test(userAgent) &&
-        !/.*Chromium\/.*/.test(userAgent)
-    } catch (error) {
-      err = error
-      debugConsole.error(err)
-    }
-
-    if (ide.browserIsSafari) {
-      ide.safariScrollPatcher = new SafariScrollPatcher($scope)
-    }
-
-    // Fix Chrome 61 and 62 text-shadow rendering
-    let browserIsChrome61or62 = false
-    try {
-      const chromeVersion =
-        parseFloat(navigator.userAgent.split(' Chrome/')[1]) || null
-      browserIsChrome61or62 = chromeVersion != null
-      if (browserIsChrome61or62) {
-        document.styleSheets[0].insertRule(
-          '.ace_editor.ace_autocomplete .ace_completion-highlight { text-shadow: none !important; font-weight: bold; }',
-          1
-        )
-      }
-    } catch (error1) {
-      err = error1
-      debugConsole.error(err)
-    }
-
     // User can append ?ft=somefeature to url to activate a feature toggle
     // User can append ?ft=somefeature to url to activate a feature toggle
     ide.featureToggle = __guard__(
     ide.featureToggle = __guard__(
       __guard__(
       __guard__(

+ 0 - 117
services/web/frontend/js/ide/SafariScrollPatcher.js

@@ -1,117 +0,0 @@
-/* eslint-disable
-    max-len,
-    no-return-assign,
-    no-unused-vars,
-*/
-// TODO: This file was created by bulk-decaffeinate.
-// Fix any style issues and re-enable lint.
-/*
- * decaffeinate suggestions:
- * DS102: Remove unnecessary code created because of implicit returns
- * DS207: Consider shorter variations of null checks
- * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
- */
-let SafariScrollPatcher
-
-export default SafariScrollPatcher = class SafariScrollPatcher {
-  constructor($scope) {
-    this.isOverAce = false // Flag to control if the pointer is over Ace.
-    this.pdfDiv = null
-    this.aceDiv = null
-
-    // Start listening to PDF wheel events when the pointer leaves the PDF region.
-    // P.S. This is the problem in a nutshell: although the pointer is elsewhere,
-    // wheel events keep being dispatched to the PDF.
-    this.handlePdfDivMouseLeave = () => {
-      return this.pdfDiv.addEventListener('wheel', this.dispatchToAce)
-    }
-
-    // Stop listening to wheel events when the pointer enters the PDF region. If
-    // the pointer is over the PDF, native behaviour is adequate.
-    this.handlePdfDivMouseEnter = () => {
-      return this.pdfDiv.removeEventListener('wheel', this.dispatchToAce)
-    }
-
-    // Set the "pointer over Ace" flag as false, when the mouse leaves its area.
-    this.handleAceDivMouseLeave = () => {
-      return (this.isOverAce = false)
-    }
-
-    // Set the "pointer over Ace" flag as true, when the mouse enters its area.
-    this.handleAceDivMouseEnter = () => {
-      return (this.isOverAce = true)
-    }
-
-    // Grab the elements (pdfDiv, aceDiv) and set the "hover" event listeners.
-    // If elements are already defined, clear existing event listeners and do
-    // the process again (grab elements, set listeners).
-    this.setListeners = () => {
-      this.isOverAce = false
-
-      // If elements aren't null, remove existing listeners.
-      if (this.pdfDiv != null) {
-        this.pdfDiv.removeEventListener(
-          'mouseleave',
-          this.handlePdfDivMouseLeave
-        )
-        this.pdfDiv.removeEventListener(
-          'mouseenter',
-          this.handlePdfDivMouseEnter
-        )
-      }
-
-      if (this.aceDiv != null) {
-        this.aceDiv.removeEventListener(
-          'mouseleave',
-          this.handleAceDivMouseLeave
-        )
-        this.aceDiv.removeEventListener(
-          'mouseenter',
-          this.handleAceDivMouseEnter
-        )
-      }
-
-      // Grab elements.
-      this.pdfDiv = document.querySelector('.pdfjs-viewer') // Grab the PDF div.
-      this.aceDiv = document.querySelector('.ace_content') // Also the editor.
-
-      // Set hover-related listeners.
-      if (this.pdfDiv != null) {
-        this.pdfDiv.addEventListener('mouseleave', this.handlePdfDivMouseLeave)
-        this.pdfDiv.addEventListener('mouseenter', this.handlePdfDivMouseEnter)
-      }
-      if (this.aceDiv != null) {
-        this.aceDiv.addEventListener('mouseleave', this.handleAceDivMouseLeave)
-        this.aceDiv.addEventListener('mouseenter', this.handleAceDivMouseEnter)
-      }
-    }
-
-    // Handler for wheel events on the PDF.
-    // If the pointer is over Ace, grab the event, prevent default behaviour
-    // and dispatch it to Ace.
-    this.dispatchToAce = e => {
-      if (this.isOverAce) {
-        // If this is logged, the problem just happened: the event arrived
-        // here (the PDF wheel handler), but it should've gone to Ace.
-
-        // Small timeout - if we dispatch immediately, an exception is thrown.
-        window.setTimeout(() => {
-          // Dispatch the exact same event to Ace (this will keep values
-          // values e.g. `wheelDelta` consistent with user interaction).
-          return this.aceDiv.dispatchEvent(e)
-        }, 1)
-
-        // Avoid scrolling the PDF, as we assume this was intended to the
-        // editor.
-        return e.preventDefault()
-      }
-    }
-
-    // "loaded" event is emitted from the pdfViewer controller $scope. This means
-    // that the previous PDF DOM element was destroyed and a new one is available,
-    // so we need to grab the elements and set the listeners again.
-    $scope.$on('loaded', () => {
-      return this.setListeners()
-    })
-  }
-}

+ 3 - 32
services/web/frontend/js/ide/editor/Document.js

@@ -85,46 +85,19 @@ export default Document = (function () {
       this.connected = this.ide.socket.socket.connected
       this.connected = this.ide.socket.socket.connected
       this.joined = false
       this.joined = false
       this.wantToBeJoined = false
       this.wantToBeJoined = false
-      this._checkAceConsistency = () => this._checkConsistency(this.ace)
       this._checkCM6Consistency = () => this._checkConsistency(this.cm6)
       this._checkCM6Consistency = () => this._checkConsistency(this.cm6)
       this._bindToEditorEvents()
       this._bindToEditorEvents()
       this._bindToSocketEvents()
       this._bindToSocketEvents()
     }
     }
 
 
     editorType() {
     editorType() {
-      if (this.ace) {
-        return 'ace'
-      } else if (this.cm6) {
+      if (this.cm6) {
         return 'cm6'
         return 'cm6'
       } else {
       } else {
         return null
         return null
       }
       }
     }
     }
 
 
-    attachToAce(ace) {
-      this.ace = ace
-      if (this.doc != null) {
-        this.doc.attachToAce(this.ace)
-      }
-      const editorDoc = this.ace.getSession().getDocument()
-      editorDoc.on('change', this._checkAceConsistency)
-      return this.ide.$scope.$emit('document:opened', this.doc)
-    }
-
-    detachFromAce() {
-      if (this.doc != null) {
-        this.doc.detachFromAce()
-      }
-      const editorDoc =
-        this.ace != null ? this.ace.getSession().getDocument() : undefined
-      if (editorDoc != null) {
-        editorDoc.off('change', this._checkAceConsistency)
-      }
-      delete this.ace
-      this.clearChaosMonkey()
-      return this.ide.$scope.$emit('document:closed', this.doc)
-    }
-
     attachToCM6(cm6) {
     attachToCM6(cm6) {
       this.cm6 = cm6
       this.cm6 = cm6
       if (this.doc != null) {
       if (this.doc != null) {
@@ -328,9 +301,7 @@ export default Document = (function () {
         }
         }
         char = copy[0]
         char = copy[0]
         copy = copy.slice(1)
         copy = copy.slice(1)
-        if (this.ace) {
-          this.ace.session.insert({ row: line, column: pos }, char)
-        } else if (this.cm6) {
+        if (this.cm6) {
           this.cm6.view.dispatch({
           this.cm6.view.dispatch({
             changes: {
             changes: {
               from: Math.min(pos, this.cm6.view.state.doc.length),
               from: Math.min(pos, this.cm6.view.state.doc.length),
@@ -757,7 +728,7 @@ export default Document = (function () {
         this.ranges.setIdSeed(old_id_seed)
         this.ranges.setIdSeed(old_id_seed)
       }
       }
       if (remote_op) {
       if (remote_op) {
-        // With remote ops, Ace hasn't been updated when we receive this op,
+        // With remote ops, the editor hasn't been updated when we receive this op,
         // so defer updating track changes until it has
         // so defer updating track changes until it has
         return setTimeout(() => this.emit('ranges:dirty'))
         return setTimeout(() => this.emit('ranges:dirty'))
       } else {
       } else {

+ 1 - 34
services/web/frontend/js/ide/editor/EditorManager.js

@@ -15,15 +15,12 @@ import _ from 'lodash'
  * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
  * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
  */
  */
 import Document from './Document'
 import Document from './Document'
-import './components/spellMenu'
-import './directives/aceEditor'
 import './directives/formattingButtons'
 import './directives/formattingButtons'
 import './directives/toggleSwitch'
 import './directives/toggleSwitch'
 import './controllers/SavingNotificationController'
 import './controllers/SavingNotificationController'
 import './controllers/CompileButton'
 import './controllers/CompileButton'
 import './controllers/SwitchToPDFButton'
 import './controllers/SwitchToPDFButton'
-import getMeta from '../../utils/meta'
-import { hasSeenCM6SwitchAwaySurvey } from '../../features/source-editor/utils/switch-away-survey'
+import '../metadata/services/metadata'
 import { debugConsole } from '@/utils/debugging'
 import { debugConsole } from '@/utils/debugging'
 
 
 let EditorManager
 let EditorManager
@@ -48,7 +45,6 @@ export default EditorManager = (function () {
         wantTrackChanges: false,
         wantTrackChanges: false,
         docTooLongErrorShown: false,
         docTooLongErrorShown: false,
         showVisual: this.showVisual(),
         showVisual: this.showVisual(),
-        newSourceEditor: this.newSourceEditor(),
         showSymbolPalette: false,
         showSymbolPalette: false,
         toggleSymbolPalette: () => {
         toggleSymbolPalette: () => {
           const newValue = !this.$scope.editor.showSymbolPalette
           const newValue = !this.$scope.editor.showSymbolPalette
@@ -171,35 +167,6 @@ export default EditorManager = (function () {
       )
       )
     }
     }
 
 
-    newSourceEditor() {
-      // Use the new source editor if the legacy editor is disabled
-      if (!getMeta('ol-showLegacySourceEditor')) {
-        return true
-      }
-
-      const storedPrefIsCM6 = () => {
-        const sourceEditor = this.localStorage(
-          `editor.source_editor.${this.$scope.project_id}`
-        )
-
-        return sourceEditor === 'cm6' || sourceEditor == null
-      }
-
-      const showCM6SwitchAwaySurvey = getMeta('ol-showCM6SwitchAwaySurvey')
-
-      if (!showCM6SwitchAwaySurvey) {
-        return storedPrefIsCM6()
-      }
-
-      if (hasSeenCM6SwitchAwaySurvey()) {
-        return storedPrefIsCM6()
-      } else {
-        // force user to switch to cm6 if they haven't seen either of the
-        // switch-away surveys
-        return true
-      }
-    }
-
     autoOpenDoc() {
     autoOpenDoc() {
       const open_doc_id =
       const open_doc_id =
         this.ide.localStorage(`doc.open_id.${this.$scope.project_id}`) ||
         this.ide.localStorage(`doc.open_id.${this.$scope.project_id}`) ||

+ 0 - 13
services/web/frontend/js/ide/editor/ShareJsDoc.js

@@ -357,19 +357,6 @@ export default ShareJsDoc = (function () {
       }
       }
     }
     }
 
 
-    attachToAce(ace) {
-      this._attachToEditor('Ace', ace, () => {
-        this._doc.attach_ace(ace, window.maxDocLength)
-      })
-    }
-
-    detachFromAce() {
-      this._maybeDetachEditorWatchdogManager()
-      return typeof this._doc.detach_ace === 'function'
-        ? this._doc.detach_ace()
-        : undefined
-    }
-
     attachToCM6(cm6) {
     attachToCM6(cm6) {
       this._attachToEditor('CM6', cm6, () => {
       this._attachToEditor('CM6', cm6, () => {
         cm6.attachShareJs(this._doc, window.maxDocLength)
         cm6.attachShareJs(this._doc, window.maxDocLength)

+ 0 - 159
services/web/frontend/js/ide/editor/ace-performance.ts

@@ -1,159 +0,0 @@
-import { round } from 'lodash'
-import grammarlyExtensionPresent from '../../shared/utils/grammarly'
-import getMeta from '../../utils/meta'
-import { debugConsole } from '@/utils/debugging'
-
-const TIMER_DOM_UPDATE_NAME = 'Ace-DomUpdate'
-const TIMER_MEASURE_NAME = 'Ace-Keypress-Measure'
-
-const sessionStart = Date.now()
-
-let performanceOptionsSupport = false
-
-// Check that performance.mark and performance.measure accept an options object
-try {
-  const testMarkName = 'featureTestMark'
-  performance.mark(testMarkName, {
-    startTime: performance.now(),
-    detail: { test: 1 },
-  })
-  performance.clearMarks(testMarkName)
-
-  const testMeasureName = 'featureTestMeasure'
-  performance.measure(testMeasureName, {
-    start: performance.now(),
-    detail: { test: 1 },
-  })
-  performance.clearMeasures(testMeasureName)
-
-  performanceOptionsSupport = true
-} catch (e) {}
-
-let performanceMemorySupport = false
-
-function measureMemoryUsage() {
-  // @ts-ignore
-  return performance.memory.usedJSHeapSize
-}
-
-try {
-  if ('memory' in window.performance) {
-    measureMemoryUsage()
-    performanceMemorySupport = true
-  }
-} catch (e) {}
-
-let keypressesSinceDomUpdateCount = 0
-const unpaintedKeypressStartTimes: number[] = []
-let animationFrameRequest: number | null = null
-
-function timeInputToRender() {
-  if (!performanceOptionsSupport) return
-
-  ++keypressesSinceDomUpdateCount
-
-  unpaintedKeypressStartTimes.push(performance.now())
-
-  if (!animationFrameRequest) {
-    animationFrameRequest = window.requestAnimationFrame(() => {
-      animationFrameRequest = null
-
-      performance.mark(TIMER_DOM_UPDATE_NAME, {
-        detail: { keypressesSinceDomUpdateCount },
-      })
-      keypressesSinceDomUpdateCount = 0
-
-      const keypressEnd = performance.now()
-
-      for (const keypressStart of unpaintedKeypressStartTimes) {
-        performance.measure(TIMER_MEASURE_NAME, {
-          start: keypressStart,
-          end: keypressEnd,
-        })
-      }
-      unpaintedKeypressStartTimes.length = 0
-    })
-  }
-}
-
-export function initAcePerfListener(textareaEl: HTMLTextAreaElement) {
-  textareaEl?.addEventListener('beforeinput', timeInputToRender)
-}
-
-export function tearDownAcePerfListener(textareaEl: HTMLTextAreaElement) {
-  textareaEl?.removeEventListener('beforeinput', timeInputToRender)
-}
-
-function calculateMean(durations: number[]) {
-  if (durations.length === 0) return 0
-
-  const sum = durations.reduce((acc, entry) => acc + entry, 0)
-  return sum / durations.length
-}
-
-export function reportAcePerf() {
-  const durations = performance
-    .getEntriesByName(TIMER_MEASURE_NAME, 'measure')
-    .map(({ duration }) => duration)
-
-  performance.clearMeasures(TIMER_MEASURE_NAME)
-
-  const meanKeypressPaint = round(calculateMean(durations), 2)
-
-  const grammarly = grammarlyExtensionPresent()
-  const sessionLength = Math.floor((Date.now() - sessionStart) / 1000) // In seconds
-
-  const memory = performanceMemorySupport ? measureMemoryUsage() : null
-
-  // Get entries for keypress counts between DOM updates
-  const domUpdateEntries = performance.getEntriesByName(
-    TIMER_DOM_UPDATE_NAME,
-    'mark'
-  ) as PerformanceMark[]
-
-  performance.clearMarks(TIMER_DOM_UPDATE_NAME)
-
-  let lags = 0
-  let nonLags = 0
-  let longestLag = 0
-  let totalKeypressCount = 0
-
-  for (const entry of domUpdateEntries) {
-    const keypressCount = entry.detail.keypressesSinceDomUpdateCount
-    if (keypressCount === 1) {
-      ++nonLags
-    } else if (keypressCount > 1) {
-      ++lags
-    }
-    if (keypressCount > longestLag) {
-      longestLag = keypressCount
-    }
-    totalKeypressCount += keypressCount
-  }
-
-  const meanLagsPerMeasure = round(lags / (lags + nonLags), 4)
-  const meanKeypressesPerMeasure = round(
-    totalKeypressCount / (lags + nonLags),
-    4
-  )
-
-  const release = getMeta('ol-ExposedSettings')?.sentryRelease || null
-
-  return {
-    numberOfEntries: durations.length,
-    meanKeypressPaint,
-    grammarly,
-    sessionLength,
-    memory,
-    lags,
-    nonLags,
-    longestLag,
-    meanLagsPerMeasure,
-    meanKeypressesPerMeasure,
-    release,
-  }
-}
-
-window._reportAcePerf = () => {
-  debugConsole.warn(reportAcePerf())
-}

+ 0 - 46
services/web/frontend/js/ide/editor/components/spellMenu.js

@@ -1,46 +0,0 @@
-/* eslint-disable
-    max-len,
-*/
-// TODO: This file was created by bulk-decaffeinate.
-// Fix any style issues and re-enable lint.
-/*
- * decaffeinate suggestions:
- * DS102: Remove unnecessary code created because of implicit returns
- * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
- */
-import App from '../../../base'
-
-export default App.component('spellMenu', {
-  bindings: {
-    open: '<',
-    top: '<',
-    left: '<',
-    layoutFromBottom: '<',
-    highlight: '<',
-    replaceWord: '&',
-    learnWord: '&',
-  },
-  template: `\
-<div
-class="dropdown context-menu spell-check-menu"
-ng-show="$ctrl.open"
-ng-style="{top: $ctrl.top, left: $ctrl.left}"
-ng-class="{open: $ctrl.open, 'spell-check-menu-from-bottom': $ctrl.layoutFromBottom}"
->
-<ul class="dropdown-menu">
-  <li ng-repeat="suggestion in $ctrl.highlight.suggestions | limitTo:8">
-    <button
-      class="btn-link text-left dropdown-menu-button"
-      ng-click="$ctrl.replaceWord({ highlight: $ctrl.highlight, suggestion: suggestion })"
-    >
-      {{ suggestion }}
-    </a>
-  </li>
-  <li class="divider"></li>
-  <li>
-    <a href ng-click="$ctrl.learnWord({ highlight: $ctrl.highlight })">Add to Dictionary</a>
-  </li>
-</ul>
-</div>\
-`,
-})

+ 0 - 1012
services/web/frontend/js/ide/editor/directives/aceEditor.js

@@ -1,1012 +0,0 @@
-import _ from 'lodash'
-/* eslint-disable
-    camelcase,
-    max-len
- */
-import App from '../../../base'
-import UndoManager from './aceEditor/undo/UndoManager'
-import AutoCompleteManager from './aceEditor/auto-complete/AutoCompleteManager'
-import SpellCheckManager from './aceEditor/spell-check/SpellCheckManager'
-import SpellCheckAdapter from './aceEditor/spell-check/SpellCheckAdapter'
-import HighlightsManager from './aceEditor/highlights/HighlightsManager'
-import CursorPositionManager from './aceEditor/cursor-position/CursorPositionManager'
-import CursorPositionAdapter from './aceEditor/cursor-position/CursorPositionAdapter'
-import TrackChangesManager from './aceEditor/track-changes/TrackChangesManager'
-import TrackChangesAdapter from './aceEditor/track-changes/TrackChangesAdapter'
-import MetadataManager from './aceEditor/metadata/MetadataManager'
-import 'ace/ace'
-import 'ace/ext-searchbox'
-import 'ace/ext-modelist'
-import 'ace/keybinding-vim'
-import '../../metadata/services/metadata'
-import '../../graphics/services/graphics'
-import '../../preamble/services/preamble'
-import '../../files/services/files'
-import {
-  initAcePerfListener,
-  tearDownAcePerfListener,
-} from '../ace-performance'
-let syntaxValidationEnabled
-const { EditSession } = ace.require('ace/edit_session')
-const ModeList = ace.require('ace/ext/modelist')
-const { Vim } = ace.require('ace/keyboard/vim')
-const SearchBox = ace.require('ace/ext/searchbox')
-
-// Set the base path that ace will fetch modes/snippets/workers from
-if (window.aceBasePath !== '') {
-  syntaxValidationEnabled = true
-  ace.config.set('basePath', window.aceBasePath)
-  ace.config.set('workerPath', window.aceBasePath)
-} else {
-  syntaxValidationEnabled = false
-}
-
-// By default, don't use workers - enable them per-session as required
-ace.config.setDefaultValue('session', 'useWorker', false)
-
-// Ace loads its script itself, so we need to hook in to be able to clear
-// the cache.
-if (ace.config._moduleUrl == null) {
-  ace.config._moduleUrl = ace.config.moduleUrl
-  ace.config.moduleUrl = function (...args) {
-    const url = ace.config._moduleUrl(...Array.from(args || []))
-    return url
-  }
-}
-
-App.directive('aceEditor', [
-  'ide',
-  '$compile',
-  '$rootScope',
-  'eventTracking',
-  'localStorage',
-  '$cacheFactory',
-  'metadata',
-  'graphics',
-  'preamble',
-  'files',
-  '$http',
-  '$q',
-  function (
-    ide,
-    $compile,
-    $rootScope,
-    eventTracking,
-    localStorage,
-    $cacheFactory,
-    metadata,
-    graphics,
-    preamble,
-    files,
-    $http,
-    $q
-  ) {
-    monkeyPatchSearch($rootScope, $compile)
-
-    return {
-      scope: {
-        theme: '=',
-        showPrintMargin: '=',
-        keybindings: '=',
-        fontSize: '=',
-        autoComplete: '=',
-        autoPairDelimiters: '=',
-        sharejsDoc: '=',
-        spellCheck: '=',
-        spellCheckLanguage: '=',
-        highlights: '=',
-        text: '=',
-        readOnly: '=',
-        annotations: '=',
-        navigateHighlights: '=',
-        fileName: '=',
-        onCtrlEnter: '=', // Compile
-        onCtrlJ: '=', // Toggle the review panel
-        onCtrlShiftC: '=', // Add a new comment
-        onCtrlShiftA: '=', // Toggle track-changes on/off
-        onSave: '=', // Cmd/Ctrl-S or :w in Vim
-        syntaxValidation: '=',
-        reviewPanel: '=',
-        eventsBridge: '=',
-        trackChanges: '=',
-        docId: '=',
-        rendererData: '=',
-        lineHeight: '=',
-        fontFamily: '=',
-      },
-      link(scope, element, attrs) {
-        // Don't freak out if we're already in an apply callback
-        let spellCheckManager
-        scope.$originalApply = scope.$apply
-        scope.$apply = function (fn) {
-          if (fn == null) {
-            fn = function () {}
-          }
-          const phase = this.$root.$$phase
-          if (phase === '$apply' || phase === '$digest') {
-            return fn()
-          } else {
-            return this.$originalApply(fn)
-          }
-        }
-
-        const editor = ace.edit(element.find('.ace-editor-body')[0])
-        editor.$blockScrolling = Infinity
-
-        // Besides the main editor, other elements will re-use this directive
-        //  for displaying read-only content -- e.g. the history panes.
-        const editorAcceptsChanges = attrs.aceEditor === 'editor'
-        if (editorAcceptsChanges) {
-          // end-to-end check for edits -> acks, globally on any doc
-          // This may catch a missing attached ShareJsDoc that in turn bails out
-          //  on missing acks.
-          ide.globalEditorWatchdogManager.attachToEditor('Ace', editor)
-        }
-
-        // auto-insertion of braces, brackets, dollars
-        editor.setOption('behavioursEnabled', scope.autoPairDelimiters || false)
-        editor.setOption('wrapBehavioursEnabled', false)
-
-        ide.$scope.$on('editor:replace-selection', (event, text) => {
-          editor.focus()
-          const document = editor.session.getDocument()
-          const ranges = editor.selection.getAllRanges()
-          for (const range of ranges) {
-            document.replace(range, text)
-          }
-        })
-
-        ide.$scope.$on('symbol-palette-toggled', (event, isToggled) => {
-          if (!isToggled) {
-            editor.focus()
-          }
-        })
-
-        scope.$watch('autoPairDelimiters', autoPairDelimiters => {
-          if (autoPairDelimiters) {
-            return editor.setOption('behavioursEnabled', true)
-          } else {
-            return editor.setOption('behavioursEnabled', false)
-          }
-        })
-
-        if (!window._debug_editors) {
-          window._debug_editors = []
-        }
-        window._debug_editors.push(editor)
-
-        scope.name = attrs.aceEditor
-
-        if (scope.spellCheck) {
-          // only enable spellcheck when explicitly required
-          spellCheckManager = new SpellCheckManager(
-            scope,
-            $cacheFactory,
-            $http,
-            $q,
-            new SpellCheckAdapter(editor)
-          )
-        }
-
-        /* eslint-disable no-unused-vars */
-        const undoManager = new UndoManager(editor)
-        const highlightsManager = new HighlightsManager(scope, editor, element)
-        const cursorPositionManager = new CursorPositionManager(
-          scope,
-          new CursorPositionAdapter(editor),
-          localStorage
-        )
-        const trackChangesManager = new TrackChangesManager(
-          scope,
-          editor,
-          element,
-          new TrackChangesAdapter(editor)
-        )
-
-        const metadataManager = new MetadataManager(
-          scope,
-          editor,
-          element,
-          metadata
-        )
-        const autoCompleteManager = new AutoCompleteManager(
-          scope,
-          editor,
-          element,
-          metadataManager,
-          graphics,
-          preamble,
-          files
-        )
-
-        // prevent user entering null and non-BMP unicode characters in Ace
-        const BAD_CHARS_REGEXP = /[\0\uD800-\uDFFF]/g
-        const BAD_CHARS_REPLACEMENT_CHAR = '\uFFFD'
-        // the 'exec' event fires for ace functions before they are executed.
-        // you can modify the input or reject the event with e.preventDefault()
-        editor.commands.on('exec', function (e) {
-          // replace bad characters in paste content
-          if (e.command && e.command.name === 'paste') {
-            BAD_CHARS_REGEXP.lastIndex = 0 // reset stateful regexp for this usage
-            if (e.args && BAD_CHARS_REGEXP.test(e.args.text)) {
-              e.args.text = e.args.text.replace(
-                BAD_CHARS_REGEXP,
-                BAD_CHARS_REPLACEMENT_CHAR
-              )
-            }
-          }
-          // replace bad characters in keyboard input
-          if (e.command && e.command.name === 'insertstring') {
-            BAD_CHARS_REGEXP.lastIndex = 0 // reset stateful regexp for this usage
-            if (e.args && BAD_CHARS_REGEXP.test(e.args)) {
-              e.args = e.args.replace(
-                BAD_CHARS_REGEXP,
-                BAD_CHARS_REPLACEMENT_CHAR
-              )
-            }
-          }
-        })
-
-        /* eslint-enable no-unused-vars */
-
-        Vim.defineEx('write', 'w', function () {
-          window.dispatchEvent(new Event('pdf:recompile'))
-        })
-        editor.commands.removeCommand('transposeletters')
-        editor.commands.removeCommand('showSettingsMenu')
-        editor.commands.removeCommand('foldall')
-
-        // For European keyboards, the / is above 7 so needs Shift pressing.
-        // This comes through as Command-Shift-/ on OS X, which is mapped to
-        // toggleBlockComment.
-        // This doesn't do anything for LaTeX, so remap this to togglecomment to
-        // work for European keyboards as normal.
-        // On Windows, the key combo comes as Ctrl-Shift-7.
-        editor.commands.removeCommand('toggleBlockComment')
-        editor.commands.removeCommand('togglecomment')
-
-        editor.commands.addCommand({
-          name: 'togglecomment',
-          bindKey: {
-            win: 'Ctrl-/|Ctrl-Shift-7',
-            mac: 'Command-/|Command-Shift-/',
-          },
-          exec(editor) {
-            return editor.toggleCommentLines()
-          },
-          multiSelectAction: 'forEachLine',
-          scrollIntoView: 'selectionPart',
-        })
-
-        // Trigger search AND replace on CMD+F
-        editor.commands.addCommand({
-          name: 'find',
-          bindKey: {
-            win: 'Ctrl-F',
-            mac: 'Command-F',
-          },
-          exec(editor) {
-            return SearchBox.Search(editor, true)
-          },
-          readOnly: true,
-        })
-
-        // Bold text on CMD+B
-        editor.commands.addCommand({
-          name: 'bold',
-          bindKey: {
-            win: 'Ctrl-B',
-            mac: 'Command-B',
-          },
-          exec(editor) {
-            const selection = editor.getSelection()
-            if (selection.isEmpty()) {
-              editor.insert('\\textbf{}')
-              return editor.navigateLeft(1)
-            } else {
-              const text = editor.getCopyText()
-              return editor.insert(`\\textbf{${text}}`)
-            }
-          },
-          readOnly: false,
-        })
-
-        // Italicise text on CMD+I
-        editor.commands.addCommand({
-          name: 'italics',
-          bindKey: {
-            win: 'Ctrl-I',
-            mac: 'Command-I',
-          },
-          exec(editor) {
-            const selection = editor.getSelection()
-            if (selection.isEmpty()) {
-              editor.insert('\\textit{}')
-              return editor.navigateLeft(1)
-            } else {
-              const text = editor.getCopyText()
-              return editor.insert(`\\textit{${text}}`)
-            }
-          },
-          readOnly: false,
-        })
-
-        scope.$watch('onCtrlEnter', function (callback) {
-          if (callback != null) {
-            return editor.commands.addCommand({
-              name: 'compile',
-              bindKey: {
-                win: 'Ctrl-Enter',
-                mac: 'Command-Enter',
-              },
-              exec: editor => {
-                return callback()
-              },
-              readOnly: true,
-            })
-          }
-        })
-
-        scope.$watch('onCtrlJ', function (callback) {
-          if (callback != null) {
-            return editor.commands.addCommand({
-              name: 'toggle-review-panel',
-              bindKey: {
-                win: 'Ctrl-J',
-                mac: 'Command-J',
-              },
-              exec: editor => {
-                return callback()
-              },
-              readOnly: true,
-            })
-          }
-        })
-
-        scope.$watch('onCtrlShiftC', function (callback) {
-          if (callback != null) {
-            return editor.commands.addCommand({
-              name: 'add-new-comment',
-              bindKey: {
-                win: 'Ctrl-Shift-C',
-                mac: 'Command-Shift-C',
-              },
-              exec: editor => {
-                return callback()
-              },
-              readOnly: true,
-            })
-          }
-        })
-
-        scope.$watch('onCtrlShiftA', function (callback) {
-          if (callback != null) {
-            return editor.commands.addCommand({
-              name: 'toggle-track-changes',
-              bindKey: {
-                win: 'Ctrl-Shift-A',
-                mac: 'Command-Shift-A',
-              },
-              exec: editor => {
-                return callback()
-              },
-              readOnly: true,
-            })
-          }
-        })
-
-        // Make '/' work for search in vim mode.
-        editor.showCommandLine = arg => {
-          if (arg === '/') {
-            return SearchBox.Search(editor, true)
-          }
-        }
-
-        const getCursorScreenPosition = function () {
-          const session = editor.getSession()
-          const cursorPosition = session.selection.getCursor()
-          const sessionPos = session.documentToScreenPosition(
-            cursorPosition.row,
-            cursorPosition.column
-          )
-          return (
-            sessionPos.row * editor.renderer.lineHeight - session.getScrollTop()
-          )
-        }
-
-        if (attrs.resizeOn != null) {
-          for (const event of Array.from(attrs.resizeOn.split(','))) {
-            scope.$on(event, function () {
-              scope.$applyAsync(() => {
-                const previousScreenPosition = getCursorScreenPosition()
-                editor.resize()
-                // Put cursor back to same vertical position on screen
-                const newScreenPosition = getCursorScreenPosition()
-                const session = editor.getSession()
-                return session.setScrollTop(
-                  session.getScrollTop() +
-                    newScreenPosition -
-                    previousScreenPosition
-                )
-              })
-            })
-          }
-        }
-
-        scope.$on(`${scope.name}:set-scroll-size`, function (e, size) {
-          // Make sure that the editor has enough scroll margin above and below
-          // to scroll the review panel with the given size
-          const marginTop = size.overflowTop
-          const { maxHeight } = editor.renderer.layerConfig
-          const marginBottom = Math.max(size.height - maxHeight, 0)
-          return setScrollMargins(marginTop, marginBottom)
-        })
-
-        function setScrollMargins(marginTop, marginBottom) {
-          let marginChanged = false
-          if (editor.renderer.scrollMargin.top !== marginTop) {
-            editor.renderer.scrollMargin.top = marginTop
-            marginChanged = true
-          }
-          if (editor.renderer.scrollMargin.bottom !== marginBottom) {
-            editor.renderer.scrollMargin.bottom = marginBottom
-            marginChanged = true
-          }
-          if (marginChanged) {
-            return editor.renderer.updateFull()
-          }
-        }
-
-        const resetScrollMargins = () => setScrollMargins(0, 0)
-
-        scope.$watch('theme', value => editor.setTheme(`ace/theme/${value}`))
-
-        scope.$watch('showPrintMargin', value =>
-          editor.setShowPrintMargin(value)
-        )
-
-        scope.$watch('keybindings', function (value) {
-          if (['vim', 'emacs'].includes(value)) {
-            return editor.setKeyboardHandler(`ace/keyboard/${value}`)
-          } else {
-            return editor.setKeyboardHandler(null)
-          }
-        })
-
-        scope.$watch('fontSize', value =>
-          element.find('.ace_editor, .ace_content').css({
-            'font-size': value + 'px',
-          })
-        )
-
-        scope.$watch('fontFamily', function (value) {
-          const monospaceFamilies = [
-            'Monaco',
-            'Menlo',
-            'Ubuntu Mono',
-            'Consolas',
-            'monospace',
-          ]
-
-          if (value != null) {
-            switch (value) {
-              case 'monaco':
-                return editor.setOption(
-                  'fontFamily',
-                  monospaceFamilies.join(', ')
-                )
-              case 'lucida':
-                return editor.setOption(
-                  'fontFamily',
-                  '"Lucida Console", "Source Code Pro", monospace'
-                )
-              default:
-                return editor.setOption('fontFamily', null)
-            }
-          }
-        })
-
-        scope.$watch('lineHeight', function (value) {
-          if (value != null) {
-            switch (value) {
-              case 'compact':
-                editor.container.style.lineHeight = 1.33
-                break
-              case 'normal':
-                editor.container.style.lineHeight = 1.6
-                break
-              case 'wide':
-                editor.container.style.lineHeight = 2
-                break
-              default:
-                editor.container.style.lineHeight = 1.6
-            }
-            return editor.renderer.updateFontSize()
-          }
-        })
-
-        scope.$watch('sharejsDoc', function (sharejs_doc, old_sharejs_doc) {
-          if (old_sharejs_doc != null) {
-            scope.$broadcast('beforeChangeDocument')
-            detachFromAce(old_sharejs_doc)
-          }
-          if (sharejs_doc != null) {
-            attachToAce(sharejs_doc)
-          }
-          if (sharejs_doc != null && old_sharejs_doc != null) {
-            return scope.$broadcast('afterChangeDocument')
-          }
-        })
-
-        scope.$watch('text', function (text) {
-          if (text != null) {
-            editor.setValue(text, -1)
-            const session = editor.getSession()
-            return session.setUseWrapMode(true)
-          }
-        })
-
-        scope.$watch('annotations', function (annotations) {
-          const session = editor.getSession()
-          return session.setAnnotations(annotations)
-        })
-
-        scope.$watch('readOnly', value => editor.setReadOnly(!!value))
-
-        scope.$watch('syntaxValidation', function (value) {
-          // ignore undefined settings here
-          // only instances of ace with an explicit value should set useWorker
-          // the history instance will have syntaxValidation undefined
-          if (value != null && syntaxValidationEnabled) {
-            const session = editor.getSession()
-            return session.setOption('useWorker', value)
-          }
-        })
-
-        editor.setOption('scrollPastEnd', true)
-
-        let updateCount = 0
-        const onChange = function () {
-          updateCount++
-
-          if (updateCount === 100) {
-            eventTracking.send('editor-interaction', 'multi-doc-update')
-          }
-          return scope.$emit(`${scope.name}:change`)
-        }
-
-        let currentFirstVisibleRow = null
-        const emitMiddleVisibleRowChanged = () => {
-          const firstVisibleRow = editor.getFirstVisibleRow()
-          if (firstVisibleRow === currentFirstVisibleRow) return
-
-          currentFirstVisibleRow = firstVisibleRow
-          const lastVisibleRow = editor.getLastVisibleRow()
-          scope.$emit(
-            `scroll:editor:update`,
-            Math.floor((firstVisibleRow + lastVisibleRow) / 2)
-          )
-        }
-
-        const onScroll = function (scrollTop) {
-          if (scope.eventsBridge == null) {
-            return
-          }
-          const height = editor.renderer.layerConfig.maxHeight
-          emitMiddleVisibleRowChanged()
-          return scope.eventsBridge.emit('aceScroll', scrollTop, height)
-        }
-
-        const onScrollbarVisibilityChanged = function (event, vRenderer) {
-          if (scope.eventsBridge == null) {
-            return
-          }
-          return scope.eventsBridge.emit(
-            'aceScrollbarVisibilityChanged',
-            vRenderer.scrollBarV.isVisible,
-            vRenderer.scrollBarV.width
-          )
-        }
-
-        if (scope.eventsBridge != null) {
-          editor.renderer.on(
-            'scrollbarVisibilityChanged',
-            onScrollbarVisibilityChanged
-          )
-
-          scope.eventsBridge.on('externalScroll', position =>
-            editor.getSession().setScrollTop(position)
-          )
-          scope.eventsBridge.on('refreshScrollPosition', function () {
-            const session = editor.getSession()
-            session.setScrollTop(session.getScrollTop() + 1)
-            return session.setScrollTop(session.getScrollTop() - 1)
-          })
-        }
-
-        const onSessionChangeForSpellCheck = function (e) {
-          spellCheckManager.onSessionChange()
-          if (e.oldSession != null) {
-            e.oldSession.getDocument().off('change', spellCheckManager.onChange)
-          }
-          e.session.getDocument().on('change', spellCheckManager.onChange)
-          if (e.oldSession != null) {
-            e.oldSession.off('changeScrollTop', spellCheckManager.onScroll)
-          }
-          return e.session.on('changeScrollTop', spellCheckManager.onScroll)
-        }
-
-        const initSpellCheck = function () {
-          if (!spellCheckManager) return
-          spellCheckManager.init()
-          editor.on('changeSession', onSessionChangeForSpellCheck)
-          onSessionChangeForSpellCheck({
-            session: editor.getSession(),
-          }) // Force initial setup
-          return editor.on('nativecontextmenu', spellCheckManager.onContextMenu)
-        }
-
-        const tearDownSpellCheck = function () {
-          if (!spellCheckManager) return
-          editor.off('changeSession', onSessionChangeForSpellCheck)
-          return editor.off(
-            'nativecontextmenu',
-            spellCheckManager.onContextMenu
-          )
-        }
-
-        const initTrackChanges = function () {
-          if (!trackChangesManager) return
-
-          trackChangesManager.rangesTracker = scope.sharejsDoc.ranges
-
-          // Force onChangeSession in order to set up highlights etc.
-          trackChangesManager.onChangeSession()
-
-          editor.on('changeSelection', trackChangesManager.onChangeSelection)
-
-          // Selection also moves with updates elsewhere in the document
-          editor.on('change', trackChangesManager.onChangeSelection)
-
-          editor.on('changeSession', trackChangesManager.onChangeSession)
-          editor.on('cut', trackChangesManager.onCut)
-          editor.on('paste', trackChangesManager.onPaste)
-          editor.renderer.on('resize', trackChangesManager.onResize)
-        }
-
-        const tearDownTrackChanges = function () {
-          if (!trackChangesManager) return
-          trackChangesManager.tearDown()
-          editor.off('changeSelection', trackChangesManager.onChangeSelection)
-
-          editor.off('change', trackChangesManager.onChangeSelection)
-          editor.off('changeSession', trackChangesManager.onChangeSession)
-          editor.off('cut', trackChangesManager.onCut)
-          editor.off('paste', trackChangesManager.onPaste)
-          editor.renderer.off('resize', trackChangesManager.onResize)
-        }
-
-        const initUndo = function () {
-          // Emulate onChangeSession event. Note: listening to changeSession
-          // event is unnecessary since this method is called when we switch
-          // sessions (via ShareJS changing) anyway
-          undoManager.onChangeSession(editor.getSession())
-          editor.on('change', undoManager.onChange)
-        }
-
-        const tearDownUndo = function () {
-          editor.off('change', undoManager.onChange)
-        }
-
-        const onSessionChangeForCursorPosition = function (e) {
-          if (e.oldSession != null) {
-            e.oldSession.selection.off(
-              'changeCursor',
-              cursorPositionManager.onCursorChange
-            )
-          }
-          return e.session.selection.on(
-            'changeCursor',
-            cursorPositionManager.onCursorChange
-          )
-        }
-
-        const onUnloadForCursorPosition = () =>
-          cursorPositionManager.onUnload(editor.getSession())
-
-        const initCursorPosition = function () {
-          editor.on('changeSession', onSessionChangeForCursorPosition)
-
-          // Force initial setup
-          onSessionChangeForCursorPosition({ session: editor.getSession() })
-
-          return $(window).on('unload', onUnloadForCursorPosition)
-        }
-
-        const tearDownCursorPosition = function () {
-          editor.off('changeSession', onSessionChangeForCursorPosition)
-          return $(window).off('unload', onUnloadForCursorPosition)
-        }
-
-        initCursorPosition()
-
-        // Trigger the event once *only* - this is called after Ace is connected
-        // to the ShareJs instance but this event should only be triggered the
-        // first time the editor is opened. Not every time the docs opened
-        const triggerEditorInitEvent = _.once(() =>
-          scope.$broadcast('editorInit')
-        )
-
-        function attachToAce(sharejs_doc) {
-          let mode
-          const lines = sharejs_doc.getSnapshot().split('\n')
-          let session = editor.getSession()
-          if (session != null) {
-            session.destroy()
-          }
-
-          // see if we can lookup a suitable mode from ace
-          // but fall back to text by default
-          try {
-            if (/\.(Rtex|bbl|tikz)$/i.test(scope.fileName)) {
-              // recognise Rtex and bbl as latex
-              mode = 'ace/mode/latex'
-            } else if (/\.(sty|cls|clo)$/.test(scope.fileName)) {
-              // recognise some common files as tex
-              mode = 'ace/mode/tex'
-            } else {
-              ;({ mode } = ModeList.getModeForPath(scope.fileName))
-              // we prefer plain_text mode over text mode because ace's
-              // text mode is actually for code and has unwanted
-              // indenting (see wrapMethod in ace edit_session.js)
-              if (mode === 'ace/mode/text') {
-                mode = 'ace/mode/plain_text'
-              }
-            }
-          } catch (error) {
-            mode = 'ace/mode/plain_text'
-          }
-
-          // create our new session
-          session = new EditSession(lines, mode)
-
-          session.setUseWrapMode(true)
-          // use syntax validation only when explicitly set
-          if (
-            scope.syntaxValidation != null &&
-            syntaxValidationEnabled &&
-            !/\.bib$/.test(scope.fileName)
-          ) {
-            session.setOption('useWorker', scope.syntaxValidation)
-          }
-
-          // set to readonly until document change handlers are attached
-          editor.setReadOnly(true)
-
-          // now attach session to editor
-          editor.setSession(session)
-
-          initAcePerfListener(editor.textInput.getElement())
-
-          const doc = session.getDocument()
-          doc.on('change', onChange)
-
-          editor.initing = true
-          sharejs_doc.attachToAce(editor)
-          editor.initing = false
-
-          // now ready to edit document
-          // respect the readOnly setting, normally false
-          editor.setReadOnly(scope.readOnly)
-          triggerEditorInitEvent()
-
-          if (!scope.readOnly) {
-            initSpellCheck()
-          }
-
-          initTrackChanges()
-          initUndo()
-
-          resetScrollMargins()
-
-          // need to set annotations after attaching because attaching
-          // deletes and then inserts document content
-          session.setAnnotations(scope.annotations)
-
-          if (scope.eventsBridge != null) {
-            session.on('changeScrollTop', onScroll)
-          }
-
-          $rootScope.hasLintingError = false
-          session.on('changeAnnotation', function () {
-            // Both linter errors and compile logs are set as error annotations,
-            // however when the user types something, the compile logs are
-            // replaced with linter errors. When we check for lint errors before
-            // autocompile we are guaranteed to get linter errors
-            const hasErrors =
-              session
-                .getAnnotations()
-                .filter(
-                  annotation =>
-                    annotation.type !== 'info' &&
-                    annotation.source !== 'compile'
-                ).length > 0
-
-            if ($rootScope.hasLintingError !== hasErrors) {
-              return ($rootScope.hasLintingError = hasErrors)
-            }
-          })
-
-          setTimeout(() =>
-            // Let any listeners init themselves
-            onScroll(editor.renderer.getScrollTop())
-          )
-
-          return editor.focus()
-        }
-
-        function detachFromAce(sharejs_doc) {
-          tearDownSpellCheck()
-          tearDownTrackChanges()
-          tearDownUndo()
-
-          tearDownAcePerfListener(editor.textInput.getElement())
-
-          sharejs_doc.detachFromAce()
-          sharejs_doc.off('remoteop.recordRemote')
-
-          const session = editor.getSession()
-          session.off('changeScrollTop')
-
-          const doc = session.getDocument()
-          return doc.off('change', onChange)
-        }
-
-        if (scope.rendererData != null) {
-          editor.renderer.on('changeCharacterSize', () => {
-            scope.$apply(
-              () => (scope.rendererData.lineHeight = editor.renderer.lineHeight)
-            )
-          })
-        }
-
-        scope.$watch('rendererData', function (rendererData) {
-          if (rendererData != null) {
-            return (rendererData.lineHeight = editor.renderer.lineHeight)
-          }
-        })
-
-        scope.$on('$destroy', function () {
-          if (scope.sharejsDoc != null) {
-            scope.$broadcast('changeEditor')
-            tearDownSpellCheck()
-            tearDownCursorPosition()
-            tearDownUndo()
-            detachFromAce(scope.sharejsDoc)
-            const session = editor.getSession()
-            if (session != null) {
-              session.destroy()
-            }
-            return scope.eventsBridge.emit(
-              'aceScrollbarVisibilityChanged',
-              false,
-              0
-            )
-          }
-        })
-
-        return scope.$emit(`${scope.name}:inited`, editor)
-      },
-
-      template: `\
-<div class="ace-editor-wrapper">
-<div
-  class="undo-conflict-warning alert alert-danger small"
-  ng-show="undo.show_remote_warning"
->
-  <strong>Watch out!</strong>
-  We had to undo some of your collaborators changes before we could undo yours.
-  <a
-    href="#"
-    class="pull-right"
-    ng-click="undo.show_remote_warning = false"
-  >Dismiss</a>
-</div>
-<div class="ace-editor-body"></div>
-<spell-menu
-  open="spellMenu.open"
-  top="spellMenu.top"
-  left="spellMenu.left"
-  layout-from-bottom="spellMenu.layoutFromBottom"
-  highlight="spellMenu.highlight"
-  replace-word="replaceWord(highlight, suggestion)"
-  learn-word="learnWord(highlight)"
-></spell-menu>
-<div
-  class="annotation-label"
-  ng-show="annotationLabel.show"
-  ng-style="{
-    position: 'absolute',
-    left:     annotationLabel.left,
-    right:    annotationLabel.right,
-    bottom:   annotationLabel.bottom,
-    top:      annotationLabel.top,
-    'background-color': annotationLabel.backgroundColor
-  }"
->
-  {{ annotationLabel.text }}
-</div>
-
-<a
-  href
-  class="highlights-before-label btn btn-info btn-xs"
-  ng-show="updateLabels.highlightsBefore > 0"
-  ng-click="gotoHighlightAbove()"
->
-  <i class="fa fa-fw fa-arrow-up"></i>
-  {{ updateLabels.highlightsBefore }} more update{{ updateLabels.highlightsBefore > 1 && "" || "s" }} above
-</a>
-
-<a
-  href
-  class="highlights-after-label btn btn-info btn-xs"
-  ng-show="updateLabels.highlightsAfter > 0"
-  ng-click="gotoHighlightBelow()"
->
-  <i class="fa fa-fw fa-arrow-down"></i>
-  {{ updateLabels.highlightsAfter }} more update{{ updateLabels.highlightsAfter > 1 && "" || "s" }} below
-
-</a>
-</div>\
-`,
-    }
-  },
-])
-
-function monkeyPatchSearch($rootScope, $compile) {
-  const searchHtml = `\
-<div class="ace_search right">
-<a href type="button" action="hide" class="ace_searchbtn_close">
-  <i class="fa fa-fw fa-times"></i>
-</a>
-<div class="ace_search_form">
-  <input class="ace_search_field form-control input-sm" placeholder="Search for" spellcheck="false"></input>
-  <div class="btn-group">
-    <button type="button" action="findNext" class="ace_searchbtn next btn btn-default btn-sm">
-      <i class="fa fa-chevron-down fa-fw"></i>
-    </button>
-    <button type="button" action="findPrev" class="ace_searchbtn prev btn btn-default btn-sm">
-      <i class="fa fa-chevron-up fa-fw"></i>
-    </button>
-  </div>
-</div>
-<div class="ace_replace_form">
-  <input class="ace_search_field form-control input-sm" placeholder="Replace with" spellcheck="false"></input>
-  <div class="btn-group">
-    <button type="button" action="replaceAndFindNext" class="ace_replacebtn btn btn-default btn-sm">Replace</button>
-    <button type="button" action="replaceAll" class="ace_replacebtn btn btn-default btn-sm">All</button>
-  </div>
-</div>
-<div class="ace_search_options">
-  <div class="btn-group">
-    <button action="toggleRegexpMode" class="btn btn-default btn-sm" tooltip-placement="bottom" tooltip-append-to-body="true" tooltip="RegExp Search">.*</button>
-    <button action="toggleCaseSensitive" class="btn btn-default btn-sm" tooltip-placement="bottom" tooltip-append-to-body="true" tooltip="CaseSensitive Search">Aa</button>
-    <button action="toggleWholeWords" class="btn btn-default btn-sm" tooltip-placement="bottom" tooltip-append-to-body="true" tooltip="Whole Word Search">"..."</button>
-    <button action="searchInSelection" class="btn btn-default btn-sm" tooltip-placement="bottom" tooltip-append-to-body="true" tooltip="Search Within Selection"><i class="fa fa-align-left"></i></button>
-  </div>
-  <span class="ace_search_counter"></span>
-</div>
-<div action="toggleReplace" class="hidden"></div>
-</div>\
-`
-
-  // Remove Ace CSS
-  $('#ace_searchbox').remove()
-
-  const SB = SearchBox.SearchBox
-  const { $init } = SB.prototype
-  SB.prototype.$init = function () {
-    this.element = $compile(searchHtml)($rootScope.$new())[0]
-    return $init.apply(this)
-  }
-}

+ 0 - 473
services/web/frontend/js/ide/editor/directives/aceEditor/auto-complete/AutoCompleteManager.js

@@ -1,473 +0,0 @@
-import _ from 'lodash'
-import CommandManager from './CommandManager'
-import EnvironmentManager from './EnvironmentManager'
-import PackageManager from './PackageManager'
-import Helpers from './Helpers'
-import 'ace/ace'
-import 'ace/ext-language_tools'
-const { Range } = ace.require('ace/range')
-const aceSnippetManager = ace.require('ace/snippets').snippetManager
-
-class AutoCompleteManager {
-  constructor(
-    $scope,
-    editor,
-    element,
-    metadataManager,
-    graphics,
-    preamble,
-    files
-  ) {
-    this.$scope = $scope
-    this.editor = editor
-    this.element = element
-    this.metadataManager = metadataManager
-    this.graphics = graphics
-    this.preamble = preamble
-    this.files = files
-    this.monkeyPatchAutocomplete()
-
-    this.$scope.$watch('autoComplete', autocomplete => {
-      if (autocomplete) {
-        this.enable()
-      } else {
-        this.disable()
-      }
-    })
-
-    const onChange = change => {
-      this.onChange(change)
-    }
-
-    this.editor.on('changeSession', e => {
-      e.oldSession.off('change', onChange)
-      e.session.on('change', onChange)
-    })
-  }
-
-  enable() {
-    this.editor.setOptions({
-      enableBasicAutocompletion: true,
-      enableSnippets: true,
-      enableLiveAutocompletion: false,
-    })
-
-    const CommandCompleter = new CommandManager(this.metadataManager)
-    const SnippetCompleter = new EnvironmentManager()
-    const PackageCompleter = new PackageManager(this.metadataManager, Helpers)
-
-    const Graphics = this.graphics
-    const Preamble = this.preamble
-    const Files = this.files
-
-    const GraphicsCompleter = {
-      getCompletions(editor, session, pos, prefix, callback) {
-        const { commandFragment } = Helpers.getContext(editor, pos)
-        if (commandFragment) {
-          const match = commandFragment.match(
-            /^~?\\(includegraphics(?:\[.*])?){([^}]*, *)?(\w*)/
-          )
-          if (match) {
-            // eslint-disable-next-line no-unused-vars
-            const commandName = match[1]
-            const graphicsPaths = Preamble.getGraphicsPaths()
-            const result = []
-            for (const graphic of Graphics.getGraphicsFiles()) {
-              let { path } = graphic
-              for (const graphicsPath of graphicsPaths) {
-                if (path.indexOf(graphicsPath) === 0) {
-                  path = path.slice(graphicsPath.length)
-                  break
-                }
-              }
-              result.push({
-                caption: `\\${commandName}{${path}}`,
-                value: `\\${commandName}{${path}}`,
-                meta: 'graphic',
-                score: 50,
-              })
-            }
-            callback(null, result)
-          }
-        }
-      },
-    }
-
-    const { metadataManager } = this
-    const FilesCompleter = {
-      getCompletions: (editor, session, pos, prefix, callback) => {
-        const { commandFragment } = Helpers.getContext(editor, pos)
-        if (commandFragment) {
-          const match = commandFragment.match(/^\\(input|include){(\w*)/)
-          if (match) {
-            // eslint-disable-next-line no-unused-vars
-            const commandName = match[1]
-            const result = []
-            for (const file of Files.getTeXFiles()) {
-              if (file.id !== this.$scope.docId && !file.deleted && file.path) {
-                const { path } = file
-                const cleanPath = path.replace(/(.+)\.tex$/i, '$1')
-                result.push({
-                  caption: `\\${commandName}{${path}}`,
-                  value: `\\${commandName}{${cleanPath}}`,
-                  meta: 'file',
-                  score: 50,
-                })
-              }
-            }
-            callback(null, result)
-          }
-        }
-      },
-    }
-
-    const LabelsCompleter = {
-      getCompletions(editor, session, pos, prefix, callback) {
-        const { commandFragment } = Helpers.getContext(editor, pos)
-        if (commandFragment) {
-          const refMatch = commandFragment.match(
-            /^~?\\([a-zA-Z]*ref){([^}]*, *)?(\w*)/
-          )
-          if (refMatch) {
-            // eslint-disable-next-line no-unused-vars
-            const commandName = refMatch[1]
-            const result = []
-            if (commandName !== 'ref') {
-              // ref is in top 100 commands
-              result.push({
-                caption: `\\${commandName}{}`,
-                snippet: `\\${commandName}{}`,
-                meta: 'cross-reference',
-                score: 60,
-              })
-            }
-            for (const label of metadataManager.getAllLabels()) {
-              result.push({
-                caption: `\\${commandName}{${label}}`,
-                value: `\\${commandName}{${label}}`,
-                meta: 'cross-reference',
-                score: 50,
-              })
-            }
-            callback(null, result)
-          }
-        }
-      },
-    }
-
-    const references = this.$scope.$root._references
-    const ReferencesCompleter = {
-      getCompletions(editor, session, pos, prefix, callback) {
-        const { commandFragment } = Helpers.getContext(editor, pos)
-        if (commandFragment) {
-          const citeMatch = commandFragment.match(
-            /^~?\\([a-z]*cite[a-z]*(?:\[.*])?){([^}]*, *)?(\w*)/
-          )
-          if (citeMatch) {
-            // eslint-disable-next-line no-unused-vars
-            let [_ignore, commandName, previousArgs] = citeMatch
-            if (previousArgs == null) {
-              previousArgs = ''
-            }
-            const previousArgsCaption =
-              previousArgs.length > 8 ? '…,' : previousArgs
-            const result = []
-            result.push({
-              caption: `\\${commandName}{}`,
-              snippet: `\\${commandName}{}`,
-              meta: 'reference',
-              score: 60,
-            })
-            if (references.keys && references.keys.length > 0) {
-              references.keys.forEach(function (key) {
-                if (key != null) {
-                  result.push({
-                    caption: `\\${commandName}{${previousArgsCaption}${key}}`,
-                    value: `\\${commandName}{${previousArgs}${key}}`,
-                    meta: 'reference',
-                    score: 50,
-                  })
-                }
-              })
-              callback(null, result)
-            } else {
-              callback(null, result)
-            }
-          }
-        }
-      },
-    }
-
-    this.editor.completers = [
-      CommandCompleter,
-      SnippetCompleter,
-      PackageCompleter,
-      ReferencesCompleter,
-      LabelsCompleter,
-      GraphicsCompleter,
-      FilesCompleter,
-    ]
-  }
-
-  disable() {
-    return this.editor.setOptions({
-      enableBasicAutocompletion: false,
-      enableSnippets: false,
-    })
-  }
-
-  onChange(change) {
-    let i
-    const cursorPosition = this.editor.getCursorPosition()
-    const { end } = change
-    const { lineUpToCursor, commandFragment } = Helpers.getContext(
-      this.editor,
-      end
-    )
-    if (
-      (i = lineUpToCursor.indexOf('%')) > -1 &&
-      lineUpToCursor[i - 1] !== '\\'
-    ) {
-      return
-    }
-    const lastCharIsBackslash = lineUpToCursor.slice(-1) === '\\'
-    const lastTwoChars = lineUpToCursor.slice(-2)
-    // Don't offer autocomplete on double-backslash, backslash-colon, etc
-    if (/^\\[^a-zA-Z]$/.test(lastTwoChars)) {
-      if (this.editor.completer) {
-        this.editor.completer.detach()
-      }
-      return
-    }
-    // Check that this change was made by us, not a collaborator
-    // (Cursor is still one place behind)
-    // NOTE: this is also the case when a user backspaces over a highlighted
-    // region
-    if (
-      change.origin !== 'remote' &&
-      change.action === 'insert' &&
-      end.row === cursorPosition.row &&
-      end.column === cursorPosition.column + 1
-    ) {
-      if (
-        (commandFragment != null ? commandFragment.length : undefined) > 2 ||
-        lastCharIsBackslash
-      ) {
-        setTimeout(() => {
-          this.editor.execCommand('startAutocomplete')
-        }, 0)
-      }
-    }
-    const match = change.lines[0].match(/\\(\w+){}/)
-    if (
-      change.action === 'insert' &&
-      match &&
-      match[1] &&
-      // eslint-disable-next-line max-len
-      /(begin|end|[a-zA-Z]*ref|usepackage|[a-z]*cite[a-z]*|input|include)/.test(
-        match[1]
-      )
-    ) {
-      return setTimeout(() => {
-        this.editor.execCommand('startAutocomplete')
-      }, 0)
-    }
-  }
-
-  monkeyPatchAutocomplete() {
-    const { Autocomplete } = ace.require('ace/autocomplete')
-    const Util = ace.require('ace/autocomplete/util')
-
-    if (Autocomplete.prototype._insertMatch == null) {
-      // Only override this once since it's global but we may create multiple
-      // autocomplete handlers
-      Autocomplete.prototype._insertMatch = Autocomplete.prototype.insertMatch
-      Autocomplete.prototype.insertMatch = function (data) {
-        const { editor } = this
-
-        const pos = editor.getCursorPosition()
-        let range = new Range(pos.row, pos.column, pos.row, pos.column + 1)
-        const nextChar = editor.session.getTextRange(range)
-        // If we are in \begin{it|}, then we need to remove the trailing }
-        // since it will be adding in with the autocomplete of \begin{item}...
-        if (
-          /^\\\w+(\[[\w\\,=. ]*\])?{/.test(this.completions.filterText) &&
-          nextChar === '}'
-        ) {
-          editor.session.remove(range)
-        }
-
-        // Provide our own `insertMatch` implementation.
-        // See the `insertMatch` method of Autocomplete in
-        // `ext-language_tools.js`.
-        // We need this to account for editing existing commands, particularly
-        // when adding a prefix.
-        // We fix this by detecting when the cursor is in the middle of an
-        // existing command, and adjusting the insertions/deletions
-        // accordingly.
-        // Example:
-        //   when changing `\ref{}` to `\href{}`, ace default behaviour
-        //   is likely to end up with `\href{}ref{}`
-        if (data == null) {
-          const { completions } = this
-          const { popup } = this
-          data = popup.getData(popup.getRow())
-          data.completer = {
-            insertMatch(editor, matchData) {
-              for (range of editor.selection.getAllRanges()) {
-                const leftRange = _.clone(range)
-                const rightRange = _.clone(range)
-                // trim to left of cursor
-                const lineUpToCursor = editor
-                  .getSession()
-                  .getTextRange(
-                    new Range(
-                      range.start.row,
-                      0,
-                      range.start.row,
-                      range.start.column
-                    )
-                  )
-                // Delete back to command start, as appropriate
-                const commandStartIndex =
-                  Helpers.getLastCommandFragmentIndex(lineUpToCursor)
-                if (commandStartIndex !== -1) {
-                  leftRange.start.column = commandStartIndex
-                } else {
-                  leftRange.start.column -= completions.filterText.length
-                }
-                editor.session.remove(leftRange)
-                // look at text after cursor
-                const lineBeyondCursor = editor
-                  .getSession()
-                  .getTextRange(
-                    new Range(
-                      rightRange.start.row,
-                      rightRange.start.column,
-                      rightRange.end.row,
-                      99999
-                    )
-                  )
-
-                if (lineBeyondCursor) {
-                  const partialCommandMatch =
-                    lineBeyondCursor.match(/^([a-zA-Z0-9]+)\{/)
-                  if (partialCommandMatch) {
-                    // We've got a partial command after the cursor
-                    const commandTail = partialCommandMatch[1]
-                    // remove rest of the partial command, right of cursor
-                    rightRange.end.column +=
-                      commandTail.length - completions.filterText.length
-                    editor.session.remove(rightRange)
-                    // trim the completion text to just the command, without
-                    // braces or brackets
-                    // example: '\cite{}' -> '\cite'
-                    if (matchData.snippet != null) {
-                      matchData.snippet = matchData.snippet.replace(
-                        /[{[].*[}\]]/,
-                        ''
-                      )
-                    }
-                    if (matchData.caption != null) {
-                      matchData.caption = matchData.caption.replace(
-                        /[{[].*[}\]]/,
-                        ''
-                      )
-                    }
-                    if (matchData.value != null) {
-                      matchData.value = matchData.value.replace(
-                        /[{[].*[}\]]/,
-                        ''
-                      )
-                    }
-                  }
-                  const inArgument = lineBeyondCursor.match(/^([\w._-]+)\}(.*)/)
-                  if (inArgument) {
-                    const argumentRightOfCursor = inArgument[1]
-                    const afterArgument = inArgument[2]
-                    if (afterArgument) {
-                      rightRange.end.column =
-                        rightRange.start.column +
-                        argumentRightOfCursor.length +
-                        1
-                    }
-                    editor.session.remove(rightRange)
-                  }
-                }
-              }
-              // finally, insert the match
-              if (matchData.snippet) {
-                aceSnippetManager.insertSnippet(editor, matchData.snippet)
-              } else {
-                editor.execCommand('insertstring', matchData.value || matchData)
-              }
-            },
-          }
-        }
-
-        Autocomplete.prototype._insertMatch.call(this, data)
-      }
-
-      // Overwrite this to set autoInsert = false and set font size
-      Autocomplete.startCommand = {
-        name: 'startAutocomplete',
-        exec: editor => {
-          if (!editor.completer) {
-            editor.completer = new Autocomplete()
-          }
-          editor.completer.autoInsert = false
-          editor.completer.autoSelect = true
-          editor.completer.showPopup(editor)
-          editor.completer.cancelContextMenu()
-          const container = $(
-            editor.completer.popup != null
-              ? editor.completer.popup.container
-              : undefined
-          )
-          container.css({ 'font-size': this.$scope.fontSize + 'px' })
-          // Dynamically set width of autocomplete popup
-          const filtered =
-            editor.completer.completions &&
-            editor.completer.completions.filtered
-          if (filtered) {
-            const longestCaption = _.max(filtered.map(c => c.caption.length))
-            const longestMeta = _.max(filtered.map(c => c.meta.length))
-            const charWidth = editor.renderer.characterWidth
-            // between 280 and 700 px
-            const width = Math.max(
-              Math.min(
-                Math.round(
-                  longestCaption * charWidth +
-                    longestMeta * charWidth +
-                    5 * charWidth
-                ),
-                700
-              ),
-              280
-            )
-            container.css({ width: `${width}px` })
-          }
-          if (filtered.length === 0) {
-            editor.completer.detach()
-          }
-        },
-        bindKey: 'Ctrl-Space|Ctrl-Shift-Space|Alt-Space',
-      }
-    }
-
-    Util.retrievePrecedingIdentifier = function (text, pos, regex) {
-      let currentLineOffset = 0
-      for (let i = pos - 1; i <= 0; i++) {
-        if (text[i] === '\n') {
-          currentLineOffset = i + 1
-          break
-        }
-      }
-      const currentLine = text.slice(currentLineOffset, pos)
-      const fragment = Helpers.getLastCommandFragment(currentLine) || ''
-      return fragment
-    }
-  }
-}
-
-export default AutoCompleteManager

+ 0 - 241
services/web/frontend/js/ide/editor/directives/aceEditor/auto-complete/CommandManager.js

@@ -1,241 +0,0 @@
-import _ from 'lodash'
-/* eslint-disable
-    max-len,
-    no-return-assign,
-    no-unused-vars,
-*/
-// TODO: This file was created by bulk-decaffeinate.
-// Fix any style issues and re-enable lint.
-/*
- * decaffeinate suggestions:
- * DS101: Remove unnecessary use of Array.from
- * DS102: Remove unnecessary code created because of implicit returns
- * DS103: Rewrite code to no longer use __guard__
- * DS206: Consider reworking classes to avoid initClass
- * DS207: Consider shorter variations of null checks
- * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
- */
-import topHundred from './snippets/TopHundredSnippets'
-let CommandManager
-class Parser {
-  static initClass() {
-    // Ignore single letter commands since auto complete is moot then.
-    this.prototype.commandRegex = /\\([a-zA-Z]{2,})/
-  }
-
-  constructor(doc, prefix) {
-    this.doc = doc
-    this.prefix = prefix
-  }
-
-  parse() {
-    // Safari regex is super slow, freezes browser for minutes on end,
-    // hacky solution: limit iterations
-    let command
-    let limit = null
-    if (
-      __guard__(
-        typeof window !== 'undefined' && window !== null
-          ? window._ide
-          : undefined,
-        x => x.browserIsSafari
-      )
-    ) {
-      limit = 5000
-    }
-
-    // fully formed commands
-    const realCommands = []
-    // commands which match the prefix exactly,
-    // and could be partially typed or malformed
-    const incidentalCommands = []
-    const seen = {}
-    let iterations = 0
-    while ((command = this.nextCommand())) {
-      iterations += 1
-      if (limit && iterations > limit) {
-        return realCommands
-      }
-
-      const docState = this.doc
-
-      let optionalArgs = 0
-      while (this.consumeArgument('[', ']')) {
-        optionalArgs++
-      }
-
-      let args = 0
-      while (this.consumeArgument('{', '}')) {
-        args++
-      }
-
-      const commandHash = `${command}\\${optionalArgs}\\${args}`
-
-      if (this.prefix != null && `\\${command}` === this.prefix) {
-        incidentalCommands.push([command, optionalArgs, args])
-      } else {
-        if (seen[commandHash] == null) {
-          seen[commandHash] = true
-          realCommands.push([command, optionalArgs, args])
-        }
-      }
-
-      // Reset to before argument to handle nested commands
-      this.doc = docState
-    }
-
-    // check incidentals, see if we should pluck out a match
-    if (incidentalCommands.length > 1) {
-      const bestMatch = incidentalCommands.sort(
-        (a, b) => b[1] + b[2] - (a[1] + a[2])
-      )[0]
-      realCommands.push(bestMatch)
-    }
-
-    return realCommands
-  }
-
-  nextCommand() {
-    const i = this.doc.search(this.commandRegex)
-    if (i === -1) {
-      return false
-    } else {
-      const match = this.doc.match(this.commandRegex)[1]
-      this.doc = this.doc.substr(i + match.length + 1)
-      return match
-    }
-  }
-
-  consumeWhitespace() {
-    const match = this.doc.match(/^[ \t\n]*/m)[0]
-    return (this.doc = this.doc.substr(match.length))
-  }
-
-  consumeArgument(openingBracket, closingBracket) {
-    this.consumeWhitespace()
-
-    if (this.doc[0] === openingBracket) {
-      let i = 1
-      let bracketParity = 1
-      while (bracketParity > 0 && i < this.doc.length) {
-        if (this.doc[i] === openingBracket) {
-          bracketParity++
-        } else if (this.doc[i] === closingBracket) {
-          bracketParity--
-        }
-        i++
-      }
-
-      if (bracketParity === 0) {
-        this.doc = this.doc.substr(i)
-        return true
-      } else {
-        return false
-      }
-    } else {
-      return false
-    }
-  }
-}
-Parser.initClass()
-
-export default CommandManager = class CommandManager {
-  constructor(metadataManager) {
-    this.metadataManager = metadataManager
-  }
-
-  getCompletions(editor, session, pos, prefix, callback) {
-    const commandNames = {}
-    for (const snippet of Array.from(topHundred)) {
-      commandNames[snippet.caption.match(/\w+/)[0]] = true
-    }
-
-    const packages = this.metadataManager.getAllPackages()
-    const packageCommands = []
-    for (const pkg in packages) {
-      const snippets = packages[pkg]
-      for (const snippet of Array.from(snippets)) {
-        packageCommands.push(snippet)
-        commandNames[snippet.caption.match(/\w+/)[0]] = true
-      }
-    }
-
-    const doc = session.getValue()
-    const parser = new Parser(doc, prefix)
-    const commands = parser.parse()
-    let completions = []
-    for (const command of Array.from(commands)) {
-      if (!commandNames[command[0]]) {
-        let caption = `\\${command[0]}`
-        const score = caption === prefix ? 99 : 50
-        let snippet = caption
-        let i = 1
-        _.times(command[1], function () {
-          snippet += `[\${${i}}]`
-          caption += '[]'
-          return i++
-        })
-        _.times(command[2], function () {
-          snippet += `{\${${i}}}`
-          caption += '{}'
-          return i++
-        })
-        completions.push({
-          caption,
-          snippet,
-          meta: 'cmd',
-          score,
-        })
-      }
-    }
-    completions = completions.concat(topHundred, packageCommands)
-
-    return callback(null, completions)
-  }
-
-  loadCommandsFromDoc(doc) {
-    const parser = new Parser(doc)
-    return (this.commands = parser.parse())
-  }
-
-  getSuggestions(commandFragment) {
-    const matchingCommands = _.filter(
-      this.commands,
-      command => command[0].slice(0, commandFragment.length) === commandFragment
-    )
-
-    return _.map(matchingCommands, function (command) {
-      let completionAfterCursor, completionBeforeCursor
-      const base = `\\${commandFragment}`
-
-      let args = ''
-      _.times(command[1], () => (args = args + '[]'))
-      _.times(command[2], () => (args = args + '{}'))
-      const completionBase = command[0].slice(commandFragment.length)
-
-      const squareArgsNo = command[1]
-      const curlyArgsNo = command[2]
-      const totalArgs = squareArgsNo + curlyArgsNo
-      if (totalArgs === 0) {
-        completionBeforeCursor = completionBase
-        completionAfterCursor = ''
-      } else {
-        completionBeforeCursor = completionBase + args[0]
-        completionAfterCursor = args.slice(1)
-      }
-
-      return {
-        base,
-        completion: completionBase + args,
-        completionBeforeCursor,
-        completionAfterCursor,
-      }
-    })
-  }
-}
-
-function __guard__(value, transform) {
-  return typeof value !== 'undefined' && value !== null
-    ? transform(value)
-    : undefined
-}

+ 0 - 237
services/web/frontend/js/ide/editor/directives/aceEditor/auto-complete/EnvironmentManager.js

@@ -1,237 +0,0 @@
-import _ from 'lodash'
-/* eslint-disable
-    max-len,
-*/
-// TODO: This file was created by bulk-decaffeinate.
-// Fix any style issues and re-enable lint.
-/*
- * decaffeinate suggestions:
- * DS101: Remove unnecessary use of Array.from
- * DS102: Remove unnecessary code created because of implicit returns
- * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
- */
-import Environments from './snippets/Environments'
-let staticSnippets = Array.from(Environments.withoutSnippets).map(env => ({
-  caption: `\\begin{${env}}...`,
-  snippet: `\
-\\begin{${env}}
-\t$1
-\\end{${env}}\
-`,
-  meta: 'env',
-}))
-
-staticSnippets = staticSnippets.concat([
-  {
-    caption: '\\begin{array}...',
-    snippet: `\
-\\begin{array}{\${1:cc}}
-\t$2 & $3 \\\\\\\\
-\t$4 & $5
-\\end{array}\
-`,
-    meta: 'env',
-  },
-  {
-    caption: '\\begin{figure}...',
-    snippet: `\
-\\begin{figure}
-\t\\centering
-\t\\includegraphics{$1}
-\t\\caption{\${2:Caption}}
-\t\\label{\${3:fig:my_label}}
-\\end{figure}\
-`,
-    meta: 'env',
-  },
-  {
-    caption: '\\begin{tabular}...',
-    snippet: `\
-\\begin{tabular}{\${1:c|c}}
-\t$2 & $3 \\\\\\\\
-\t$4 & $5
-\\end{tabular}\
-`,
-    meta: 'env',
-  },
-  {
-    caption: '\\begin{table}...',
-    snippet: `\
-\\begin{table}[$1]
-\t\\centering
-\t\\begin{tabular}{\${2:c|c}}
-\t\t$3 & $4 \\\\\\\\
-\t\t$5 & $6
-\t\\end{tabular}
-\t\\caption{\${7:Caption}}
-\t\\label{\${8:tab:my_label}}
-\\end{table}\
-`,
-    meta: 'env',
-  },
-  {
-    caption: '\\begin{list}...',
-    snippet: `\
-\\begin{list}
-\t\\item $1
-\\end{list}\
-`,
-    meta: 'env',
-  },
-  {
-    caption: '\\begin{enumerate}...',
-    snippet: `\
-\\begin{enumerate}
-\t\\item $1
-\\end{enumerate}\
-`,
-    meta: 'env',
-  },
-  {
-    caption: '\\begin{itemize}...',
-    snippet: `\
-\\begin{itemize}
-\t\\item $1
-\\end{itemize}\
-`,
-    meta: 'env',
-  },
-  {
-    caption: '\\begin{frame}...',
-    snippet: `\
-\\begin{frame}{\${1:Frame Title}}
-\t$2
-\\end{frame}\
-`,
-    meta: 'env',
-  },
-])
-
-const documentSnippet = {
-  caption: '\\begin{document}...',
-  snippet: `\
-\\begin{document}
-$1
-\\end{document}\
-`,
-  meta: 'env',
-}
-
-const bibliographySnippet = {
-  caption: '\\begin{thebibliography}...',
-  snippet: `\
-\\begin{thebibliography}{$1}
-\\bibitem{$2}
-$3
-\\end{thebibliography}\
-`,
-  meta: 'env',
-}
-staticSnippets.push(documentSnippet)
-
-const parseCustomEnvironments = function (text) {
-  let match
-  const re = /^\\newenvironment{(\w+)}.*$/gm
-  const result = []
-  let iterations = 0
-  while ((match = re.exec(text))) {
-    result.push({ name: match[1], whitespace: null })
-    iterations += 1
-    if (iterations >= 1000) {
-      return result
-    }
-  }
-  return result
-}
-
-const parseBeginCommands = function (text) {
-  let match
-  const re = /^([\t ]*)\\begin{(\w+)}.*\n([\t ]*)/gm
-  const result = []
-  let iterations = 0
-  while ((match = re.exec(text))) {
-    const whitespaceAlignment = match[3].replace(match[1] || '', '')
-    if (
-      !Array.from(Environments.all).includes(match[2]) &&
-      match[2] !== 'document'
-    ) {
-      result.push({ name: match[2], whitespace: whitespaceAlignment })
-      iterations += 1
-      if (iterations >= 1000) {
-        return result
-      }
-    }
-    re.lastIndex = match.index + 1
-  }
-  return result
-}
-
-const hasDocumentEnvironment = function (text) {
-  const re = /^\\begin{document}/m
-  return re.exec(text) !== null
-}
-
-const hasBibliographyEnvironment = function (text) {
-  const re = /^\\begin{thebibliography}/m
-  return re.exec(text) !== null
-}
-
-class EnvironmentManager {
-  getCompletions(editor, session, pos, prefix, callback) {
-    let ind
-    const docText = session.getValue()
-    const customEnvironments = parseCustomEnvironments(docText)
-    const beginCommands = parseBeginCommands(docText)
-    if (hasDocumentEnvironment(docText)) {
-      ind = staticSnippets.indexOf(documentSnippet)
-      if (ind !== -1) {
-        staticSnippets.splice(ind, 1)
-      }
-    } else {
-      staticSnippets.push(documentSnippet)
-    }
-
-    if (hasBibliographyEnvironment(docText)) {
-      ind = staticSnippets.indexOf(bibliographySnippet)
-      if (ind !== -1) {
-        staticSnippets.splice(ind, 1)
-      }
-    } else {
-      staticSnippets.push(bibliographySnippet)
-    }
-
-    const parsedItemsMap = {}
-    for (const environment of Array.from(customEnvironments)) {
-      parsedItemsMap[environment.name] = environment
-    }
-    for (const command of Array.from(beginCommands)) {
-      parsedItemsMap[command.name] = command
-    }
-    const parsedItems = _.values(parsedItemsMap)
-    const snippets = staticSnippets
-      .concat(
-        parsedItems.map(item => ({
-          caption: `\\begin{${item.name}}...`,
-          snippet: `\
-\\begin{${item.name}}
-${item.whitespace || ''}$0
-\\end{${item.name}}\
-`,
-          meta: 'env',
-        }))
-      )
-      .concat(
-        // arguably these `end` commands shouldn't be here, as they're not snippets
-        // but this is where we have access to the `begin` environment names
-        // *shrug*
-        parsedItems.map(item => ({
-          caption: `\\end{${item.name}}`,
-          value: `\\end{${item.name}}`,
-          meta: 'env',
-        }))
-      )
-    return callback(null, snippets)
-  }
-}
-
-export default EnvironmentManager

+ 0 - 73
services/web/frontend/js/ide/editor/directives/aceEditor/auto-complete/Helpers.js

@@ -1,73 +0,0 @@
-/* eslint-disable
-    max-len,
-    no-cond-assign,
-*/
-// TODO: This file was created by bulk-decaffeinate.
-// Fix any style issues and re-enable lint.
-/*
- * decaffeinate suggestions:
- * DS102: Remove unnecessary code created because of implicit returns
- * DS103: Rewrite code to no longer use __guard__
- * DS207: Consider shorter variations of null checks
- * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
- */
-import 'ace/ace'
-import 'ace/ext-language_tools'
-const { Range } = ace.require('ace/range')
-
-const Helpers = {
-  getLastCommandFragment(lineUpToCursor) {
-    let index
-    if ((index = Helpers.getLastCommandFragmentIndex(lineUpToCursor)) > -1) {
-      return lineUpToCursor.slice(index)
-    } else {
-      return null
-    }
-  },
-
-  getLastCommandFragmentIndex(lineUpToCursor) {
-    // This is hack to let us skip over commands in arguments, and
-    // go to the command on the same 'level' as us. E.g.
-    //    \includegraphics[width=\textwidth]{..
-    // should not match the \textwidth.
-    let m
-    const blankArguments = lineUpToCursor.replace(/\[([^\]]*)\]/g, args =>
-      Array(args.length + 1).join('.')
-    )
-    if ((m = blankArguments.match(/(\\[^\\]*)$/))) {
-      return m.index
-    } else {
-      return -1
-    }
-  },
-
-  getCommandNameFromFragment(commandFragment) {
-    return __guard__(
-      commandFragment != null ? commandFragment.match(/\\(\w+)\{/) : undefined,
-      x => x[1]
-    )
-  },
-
-  getContext(editor, pos) {
-    const upToCursorRange = new Range(pos.row, 0, pos.row, pos.column)
-    const lineUpToCursor = editor.getSession().getTextRange(upToCursorRange)
-    const commandFragment = Helpers.getLastCommandFragment(lineUpToCursor)
-    const commandName = Helpers.getCommandNameFromFragment(commandFragment)
-    const beyondCursorRange = new Range(pos.row, pos.column, pos.row, 99999)
-    const lineBeyondCursor = editor.getSession().getTextRange(beyondCursorRange)
-    return {
-      lineUpToCursor,
-      commandFragment,
-      commandName,
-      lineBeyondCursor,
-    }
-  },
-}
-
-export default Helpers
-
-function __guard__(value, transform) {
-  return typeof value !== 'undefined' && value !== null
-    ? transform(value)
-    : undefined
-}

+ 0 - 143
services/web/frontend/js/ide/editor/directives/aceEditor/auto-complete/PackageManager.js

@@ -1,143 +0,0 @@
-/* eslint-disable
-    max-len,
-*/
-// TODO: This file was created by bulk-decaffeinate.
-// Fix any style issues and re-enable lint.
-/*
- * decaffeinate suggestions:
- * DS101: Remove unnecessary use of Array.from
- * DS102: Remove unnecessary code created because of implicit returns
- * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
- */
-const packages = [
-  'inputenc',
-  'graphicx',
-  'amsmath',
-  'geometry',
-  'amssymb',
-  'hyperref',
-  'babel',
-  'color',
-  'xcolor',
-  'url',
-  'natbib',
-  'fontenc',
-  'fancyhdr',
-  'amsfonts',
-  'booktabs',
-  'amsthm',
-  'float',
-  'tikz',
-  'caption',
-  'setspace',
-  'multirow',
-  'array',
-  'multicol',
-  'titlesec',
-  'enumitem',
-  'ifthen',
-  'listings',
-  'blindtext',
-  'subcaption',
-  'times',
-  'bm',
-  'subfigure',
-  'algorithm',
-  'fontspec',
-  'biblatex',
-  'tabularx',
-  'microtype',
-  'etoolbox',
-  'parskip',
-  'calc',
-  'verbatim',
-  'mathtools',
-  'epsfig',
-  'wrapfig',
-  'lipsum',
-  'cite',
-  'textcomp',
-  'longtable',
-  'textpos',
-  'algpseudocode',
-  'enumerate',
-  'subfig',
-  'pdfpages',
-  'epstopdf',
-  'latexsym',
-  'lmodern',
-  'pifont',
-  'ragged2e',
-  'rotating',
-  'dcolumn',
-  'xltxtra',
-  'marvosym',
-  'indentfirst',
-  'xspace',
-  'csquotes',
-  'xparse',
-  'changepage',
-  'soul',
-  'xunicode',
-  'comment',
-  'mathrsfs',
-  'tocbibind',
-  'lastpage',
-  'algorithm2e',
-  'pgfplots',
-  'lineno',
-  'graphics',
-  'algorithmic',
-  'fullpage',
-  'mathptmx',
-  'todonotes',
-  'ulem',
-  'tweaklist',
-  'moderncvstyleclassic',
-  'collection',
-  'moderncvcompatibility',
-  'gensymb',
-  'helvet',
-  'siunitx',
-  'adjustbox',
-  'placeins',
-  'colortbl',
-  'appendix',
-  'makeidx',
-  'supertabular',
-  'ifpdf',
-  'framed',
-  'aliascnt',
-  'layaureo',
-  'authblk',
-]
-
-class PackageManager {
-  constructor(metadataManager) {
-    this.metadataManager = metadataManager
-  }
-
-  getCompletions(editor, session, pos, prefix, callback) {
-    const usedPackages = Object.keys(this.metadataManager.getAllPackages())
-    const packageSnippets = []
-    for (const pkg of Array.from(packages)) {
-      if (!Array.from(usedPackages).includes(pkg)) {
-        packageSnippets.push({
-          caption: `\\usepackage{${pkg}}`,
-          snippet: `\\usepackage{${pkg}}`,
-          meta: 'pkg',
-        })
-      }
-    }
-
-    packageSnippets.push({
-      caption: '\\usepackage{}',
-      snippet: '\\usepackage{$1}',
-      meta: 'pkg',
-      score: 70,
-    })
-    return callback(null, packageSnippets)
-  }
-}
-
-export default PackageManager

+ 0 - 34
services/web/frontend/js/ide/editor/directives/aceEditor/auto-complete/snippets/Environments.js

@@ -1,34 +0,0 @@
-// TODO: This file was created by bulk-decaffeinate.
-// Fix any style issues and re-enable lint.
-const envs = [
-  'abstract',
-  'align',
-  'align*',
-  'equation',
-  'equation*',
-  'gather',
-  'gather*',
-  'multline',
-  'multline*',
-  'split',
-  'verbatim',
-  'quote',
-  'center',
-]
-
-const envsWithSnippets = [
-  'array',
-  'figure',
-  'tabular',
-  'table',
-  'list',
-  'enumerate',
-  'itemize',
-  'frame',
-  'thebibliography',
-]
-
-export default {
-  all: envs.concat(envsWithSnippets),
-  withoutSnippets: envs,
-}

+ 0 - 699
services/web/frontend/js/ide/editor/directives/aceEditor/auto-complete/snippets/TopHundredSnippets.js

@@ -1,699 +0,0 @@
-// TODO: This file was created by bulk-decaffeinate.
-// Fix any style issues and re-enable lint.
-/*
- * decaffeinate suggestions:
- * DS102: Remove unnecessary code created because of implicit returns
- * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
- */
-export default [
-  {
-    caption: '\\begin{}',
-    snippet: '\\begin{$1}',
-    meta: 'env',
-    score: 7.849662248028187,
-  },
-  {
-    caption: '\\end{}',
-    snippet: '\\end{$1}',
-    meta: 'env',
-    score: 7.847906405228455,
-  },
-  {
-    caption: '\\usepackage[]{}',
-    snippet: '\\usepackage[$1]{$2}',
-    meta: 'pkg',
-    score: 5.427890758130527,
-  },
-  {
-    caption: '\\item',
-    snippet: '\\item ',
-    meta: 'cmd',
-    score: 3.800886892251021,
-  },
-  {
-    caption: '\\item[]',
-    snippet: '\\item[$1] ',
-    meta: 'cmd',
-    score: 3.800886892251021,
-  },
-  {
-    caption: '\\section{}',
-    snippet: '\\section{$1}',
-    meta: 'cmd',
-    score: 3.0952612541683835,
-  },
-  {
-    caption: '\\textbf{}',
-    snippet: '\\textbf{$1}',
-    meta: 'cmd',
-    score: 2.627755982816738,
-  },
-  {
-    caption: '\\cite{}',
-    snippet: '\\cite{$1}',
-    meta: 'cmd',
-    score: 2.341195220791228,
-  },
-  {
-    caption: '\\label{}',
-    snippet: '\\label{$1}',
-    meta: 'cmd',
-    score: 1.897791904799601,
-  },
-  {
-    caption: '\\textit{}',
-    snippet: '\\textit{$1}',
-    meta: 'cmd',
-    score: 1.6842996195493385,
-  },
-  {
-    caption: '\\includegraphics[]{}',
-    snippet: '\\includegraphics[$1]{$2}',
-    meta: 'cmd',
-    score: 1.4595731795525781,
-  },
-  {
-    caption: '\\documentclass[]{}',
-    snippet: '\\documentclass[$1]{$2}',
-    meta: 'cmd',
-    score: 1.4425339817971206,
-  },
-  {
-    caption: '\\documentclass{}',
-    snippet: '\\documentclass{$1}',
-    meta: 'cmd',
-    score: 1.4425339817971206,
-  },
-  {
-    caption: '\\ref{}',
-    snippet: '\\ref{$1}',
-    meta: 'cross-reference',
-    score: 0.014379554883991673,
-  },
-  {
-    caption: '\\frac{}{}',
-    snippet: '\\frac{$1}{$2}',
-    meta: 'cmd',
-    score: 1.4341091141105058,
-  },
-  {
-    caption: '\\subsection{}',
-    snippet: '\\subsection{$1}',
-    meta: 'cmd',
-    score: 1.3890912739512353,
-  },
-  {
-    caption: '\\hline',
-    snippet: '\\hline',
-    meta: 'cmd',
-    score: 1.3209538327406387,
-  },
-  {
-    caption: '\\caption{}',
-    snippet: '\\caption{$1}',
-    meta: 'cmd',
-    score: 1.2569477427490174,
-  },
-  {
-    caption: '\\centering',
-    snippet: '\\centering',
-    meta: 'cmd',
-    score: 1.1642881814937829,
-  },
-  {
-    caption: '\\vspace{}',
-    snippet: '\\vspace{$1}',
-    meta: 'cmd',
-    score: 0.9533807826673939,
-  },
-  {
-    caption: '\\title{}',
-    snippet: '\\title{$1}',
-    meta: 'cmd',
-    score: 0.9202908262245683,
-  },
-  {
-    caption: '\\author{}',
-    snippet: '\\author{$1}',
-    meta: 'cmd',
-    score: 0.8973590434087177,
-  },
-  {
-    caption: '\\author[]{}',
-    snippet: '\\author[$1]{$2}',
-    meta: 'cmd',
-    score: 0.8973590434087177,
-  },
-  {
-    caption: '\\maketitle',
-    snippet: '\\maketitle',
-    meta: 'cmd',
-    score: 0.7504160124360846,
-  },
-  {
-    caption: '\\textwidth',
-    snippet: '\\textwidth',
-    meta: 'cmd',
-    score: 0.7355328080889112,
-  },
-  {
-    caption: '\\newcommand{}{}',
-    snippet: '\\newcommand{$1}{$2}',
-    meta: 'cmd',
-    score: 0.7264891987129375,
-  },
-  {
-    caption: '\\newcommand{}[]{}',
-    snippet: '\\newcommand{$1}[$2]{$3}',
-    meta: 'cmd',
-    score: 0.7264891987129375,
-  },
-  {
-    caption: '\\date{}',
-    snippet: '\\date{$1}',
-    meta: 'cmd',
-    score: 0.7225518453076786,
-  },
-  {
-    caption: '\\emph{}',
-    snippet: '\\emph{$1}',
-    meta: 'cmd',
-    score: 0.7060308784832261,
-  },
-  {
-    caption: '\\textsc{}',
-    snippet: '\\textsc{$1}',
-    meta: 'cmd',
-    score: 0.6926466355384758,
-  },
-  {
-    caption: '\\multicolumn{}{}{}',
-    snippet: '\\multicolumn{$1}{$2}{$3}',
-    meta: 'cmd',
-    score: 0.5473606021405326,
-  },
-  {
-    caption: '\\input{}',
-    snippet: '\\input{$1}',
-    meta: 'cmd',
-    score: 0.4966021927742672,
-  },
-  {
-    caption: '\\alpha',
-    snippet: '\\alpha',
-    meta: 'cmd',
-    score: 0.49520006391384913,
-  },
-  {
-    caption: '\\in',
-    snippet: '\\in',
-    meta: 'cmd',
-    score: 0.4716039670146658,
-  },
-  {
-    caption: '\\mathbf{}',
-    snippet: '\\mathbf{$1}',
-    meta: 'cmd',
-    score: 0.4682018419466319,
-  },
-  {
-    caption: '\\right',
-    snippet: '\\right',
-    meta: 'cmd',
-    score: 0.4299239459457309,
-  },
-  {
-    caption: '\\left',
-    snippet: '\\left',
-    meta: 'cmd',
-    score: 0.42937815279867964,
-  },
-  {
-    caption: '\\sum',
-    snippet: '\\sum',
-    meta: 'cmd',
-    score: 0.42607994509619934,
-  },
-  {
-    caption: '\\chapter{}',
-    snippet: '\\chapter{$1}',
-    meta: 'cmd',
-    score: 0.422097569591803,
-  },
-  {
-    caption: '\\par',
-    snippet: '\\par',
-    meta: 'cmd',
-    score: 0.413853376001159,
-  },
-  {
-    caption: '\\lambda',
-    snippet: '\\lambda',
-    meta: 'cmd',
-    score: 0.39389600578684125,
-  },
-  {
-    caption: '\\subsubsection{}',
-    snippet: '\\subsubsection{$1}',
-    meta: 'cmd',
-    score: 0.3727781330132016,
-  },
-  {
-    caption: '\\bibitem{}',
-    snippet: '\\bibitem{$1}',
-    meta: 'cmd',
-    score: 0.3689547570562042,
-  },
-  {
-    caption: '\\bibitem[]{}',
-    snippet: '\\bibitem[$1]{$2}',
-    meta: 'cmd',
-    score: 0.3689547570562042,
-  },
-  {
-    caption: '\\text{}',
-    snippet: '\\text{$1}',
-    meta: 'cmd',
-    score: 0.3608680734736821,
-  },
-  {
-    caption: '\\setlength{}{}',
-    snippet: '\\setlength{$1}{$2}',
-    meta: 'cmd',
-    score: 0.354445763583904,
-  },
-  {
-    caption: '\\mathcal{}',
-    snippet: '\\mathcal{$1}',
-    meta: 'cmd',
-    score: 0.35084018920966636,
-  },
-  {
-    caption: '\\newpage',
-    snippet: '\\newpage',
-    meta: 'cmd',
-    score: 0.3277033727934986,
-  },
-  {
-    caption: '\\renewcommand{}{}',
-    snippet: '\\renewcommand{$1}{$2}',
-    meta: 'cmd',
-    score: 0.3267437011085663,
-  },
-  {
-    caption: '\\theta',
-    snippet: '\\theta',
-    meta: 'cmd',
-    score: 0.3210417159232142,
-  },
-  {
-    caption: '\\hspace{}',
-    snippet: '\\hspace{$1}',
-    meta: 'cmd',
-    score: 0.3147206476372336,
-  },
-  {
-    caption: '\\beta',
-    snippet: '\\beta',
-    meta: 'cmd',
-    score: 0.3061799530337638,
-  },
-  {
-    caption: '\\texttt{}',
-    snippet: '\\texttt{$1}',
-    meta: 'cmd',
-    score: 0.3019066753744355,
-  },
-  {
-    caption: '\\times',
-    snippet: '\\times',
-    meta: 'cmd',
-    score: 0.2957960629411553,
-  },
-  {
-    caption: '\\color{}',
-    snippet: '\\color{$1}',
-    meta: 'cmd',
-    score: 0.2864294797053033,
-  },
-  {
-    caption: '\\mu',
-    snippet: '\\mu',
-    meta: 'cmd',
-    score: 0.27635652476799255,
-  },
-  {
-    caption: '\\bibliography{}',
-    snippet: '\\bibliography{$1}',
-    meta: 'cmd',
-    score: 0.2659628337907604,
-  },
-  {
-    caption: '\\linewidth',
-    snippet: '\\linewidth',
-    meta: 'cmd',
-    score: 0.2639498312518439,
-  },
-  {
-    caption: '\\delta',
-    snippet: '\\delta',
-    meta: 'cmd',
-    score: 0.2620578600722735,
-  },
-  {
-    caption: '\\sigma',
-    snippet: '\\sigma',
-    meta: 'cmd',
-    score: 0.25940147926344487,
-  },
-  {
-    caption: '\\pi',
-    snippet: '\\pi',
-    meta: 'cmd',
-    score: 0.25920934567729714,
-  },
-  {
-    caption: '\\hat{}',
-    snippet: '\\hat{$1}',
-    meta: 'cmd',
-    score: 0.25264309033778715,
-  },
-  {
-    caption: '\\bibliographystyle{}',
-    snippet: '\\bibliographystyle{$1}',
-    meta: 'cmd',
-    score: 0.25122317941387773,
-  },
-  {
-    caption: '\\small',
-    snippet: '\\small',
-    meta: 'cmd',
-    score: 0.2447632045426295,
-  },
-  {
-    caption: '\\LaTeX',
-    snippet: '\\LaTeX',
-    meta: 'cmd',
-    score: 0.2334089308452787,
-  },
-  {
-    caption: '\\cdot',
-    snippet: '\\cdot',
-    meta: 'cmd',
-    score: 0.23029085545522762,
-  },
-  {
-    caption: '\\footnote{}',
-    snippet: '\\footnote{$1}',
-    meta: 'cmd',
-    score: 0.2253056071787701,
-  },
-  {
-    caption: '\\newtheorem{}{}',
-    snippet: '\\newtheorem{$1}{$2}',
-    meta: 'cmd',
-    score: 0.215689795055434,
-  },
-  {
-    caption: '\\Delta',
-    snippet: '\\Delta',
-    meta: 'cmd',
-    score: 0.21386475063892618,
-  },
-  {
-    caption: '\\tau',
-    snippet: '\\tau',
-    meta: 'cmd',
-    score: 0.21236188205859796,
-  },
-  {
-    caption: '\\hfill',
-    snippet: '\\hfill',
-    meta: 'cmd',
-    score: 0.2058248088519886,
-  },
-  {
-    caption: '\\leq',
-    snippet: '\\leq',
-    meta: 'cmd',
-    score: 0.20498894440637172,
-  },
-  {
-    caption: '\\footnotesize',
-    snippet: '\\footnotesize',
-    meta: 'cmd',
-    score: 0.2038592081252624,
-  },
-  {
-    caption: '\\large',
-    snippet: '\\large',
-    meta: 'cmd',
-    score: 0.20377416734108866,
-  },
-  {
-    caption: '\\sqrt{}',
-    snippet: '\\sqrt{$1}',
-    meta: 'cmd',
-    score: 0.20240160977404634,
-  },
-  {
-    caption: '\\epsilon',
-    snippet: '\\epsilon',
-    meta: 'cmd',
-    score: 0.2005136761359043,
-  },
-  {
-    caption: '\\Large',
-    snippet: '\\Large',
-    meta: 'cmd',
-    score: 0.1987771081149759,
-  },
-  {
-    caption: '\\rho',
-    snippet: '\\rho',
-    meta: 'cmd',
-    score: 0.1959287380541684,
-  },
-  {
-    caption: '\\omega',
-    snippet: '\\omega',
-    meta: 'cmd',
-    score: 0.19326783415115262,
-  },
-  {
-    caption: '\\mathrm{}',
-    snippet: '\\mathrm{$1}',
-    meta: 'cmd',
-    score: 0.19117752976172653,
-  },
-  {
-    caption: '\\boldsymbol{}',
-    snippet: '\\boldsymbol{$1}',
-    meta: 'cmd',
-    score: 0.18137737738638837,
-  },
-  {
-    caption: '\\gamma',
-    snippet: '\\gamma',
-    meta: 'cmd',
-    score: 0.17940276535431304,
-  },
-  {
-    caption: '\\clearpage',
-    snippet: '\\clearpage',
-    meta: 'cmd',
-    score: 0.1789117552185788,
-  },
-  {
-    caption: '\\infty',
-    snippet: '\\infty',
-    meta: 'cmd',
-    score: 0.17837290019711305,
-  },
-  {
-    caption: '\\phi',
-    snippet: '\\phi',
-    meta: 'cmd',
-    score: 0.17405809173097808,
-  },
-  {
-    caption: '\\partial',
-    snippet: '\\partial',
-    meta: 'cmd',
-    score: 0.17168102367966637,
-  },
-  {
-    caption: '\\include{}',
-    snippet: '\\include{$1}',
-    meta: 'cmd',
-    score: 0.1547080054979312,
-  },
-  {
-    caption: '\\address{}',
-    snippet: '\\address{$1}',
-    meta: 'cmd',
-    score: 0.1525055392611109,
-  },
-  {
-    caption: '\\quad',
-    snippet: '\\quad',
-    meta: 'cmd',
-    score: 0.15242755832392743,
-  },
-  {
-    caption: '\\paragraph{}',
-    snippet: '\\paragraph{$1}',
-    meta: 'cmd',
-    score: 0.152074250347974,
-  },
-  {
-    caption: '\\varepsilon',
-    snippet: '\\varepsilon',
-    meta: 'cmd',
-    score: 0.05411564201390573,
-  },
-  {
-    caption: '\\zeta',
-    snippet: '\\zeta',
-    meta: 'cmd',
-    score: 0.023330249803752954,
-  },
-  {
-    caption: '\\eta',
-    snippet: '\\eta',
-    meta: 'cmd',
-    score: 0.11088718379889091,
-  },
-  {
-    caption: '\\vartheta',
-    snippet: '\\vartheta',
-    meta: 'cmd',
-    score: 0.0025822992078068712,
-  },
-  {
-    caption: '\\iota',
-    snippet: '\\iota',
-    meta: 'cmd',
-    score: 0.0024774003791525486,
-  },
-  {
-    caption: '\\kappa',
-    snippet: '\\kappa',
-    meta: 'cmd',
-    score: 0.04887876299369008,
-  },
-  {
-    caption: '\\nu',
-    snippet: '\\nu',
-    meta: 'cmd',
-    score: 0.09206962821059342,
-  },
-  {
-    caption: '\\xi',
-    snippet: '\\xi',
-    meta: 'cmd',
-    score: 0.06496042899265699,
-  },
-  {
-    caption: '\\varpi',
-    snippet: '\\varpi',
-    meta: 'cmd',
-    score: 0.0007039358167790341,
-  },
-  {
-    caption: '\\varrho',
-    snippet: '\\varrho',
-    meta: 'cmd',
-    score: 0.0011279491613898612,
-  },
-  {
-    caption: '\\varsigma',
-    snippet: '\\varsigma',
-    meta: 'cmd',
-    score: 0.0010424880711234978,
-  },
-  {
-    caption: '\\upsilon',
-    snippet: '\\upsilon',
-    meta: 'cmd',
-    score: 0.00420715572598688,
-  },
-  {
-    caption: '\\varphi',
-    snippet: '\\varphi',
-    meta: 'cmd',
-    score: 0.03351251516668212,
-  },
-  {
-    caption: '\\chi',
-    snippet: '\\chi',
-    meta: 'cmd',
-    score: 0.043373492287805675,
-  },
-  {
-    caption: '\\psi',
-    snippet: '\\psi',
-    meta: 'cmd',
-    score: 0.09994508706163642,
-  },
-  {
-    caption: '\\Gamma',
-    snippet: '\\Gamma',
-    meta: 'cmd',
-    score: 0.04801549269801977,
-  },
-  {
-    caption: '\\Theta',
-    snippet: '\\Theta',
-    meta: 'cmd',
-    score: 0.038090902146599444,
-  },
-  {
-    caption: '\\Lambda',
-    snippet: '\\Lambda',
-    meta: 'cmd',
-    score: 0.032206594305977686,
-  },
-  {
-    caption: '\\Xi',
-    snippet: '\\Xi',
-    meta: 'cmd',
-    score: 0.01060997225400494,
-  },
-  {
-    caption: '\\Pi',
-    snippet: '\\Pi',
-    meta: 'cmd',
-    score: 0.021264671817473237,
-  },
-  {
-    caption: '\\Sigma',
-    snippet: '\\Sigma',
-    meta: 'cmd',
-    score: 0.05769642802079917,
-  },
-  {
-    caption: '\\Upsilon',
-    snippet: '\\Upsilon',
-    meta: 'cmd',
-    score: 0.00032875192955749566,
-  },
-  {
-    caption: '\\Phi',
-    snippet: '\\Phi',
-    meta: 'cmd',
-    score: 0.0538724950042562,
-  },
-  {
-    caption: '\\Psi',
-    snippet: '\\Psi',
-    meta: 'cmd',
-    score: 0.03056589143021648,
-  },
-  {
-    caption: '\\Omega',
-    snippet: '\\Omega',
-    meta: 'cmd',
-    score: 0.09490387997853639,
-  },
-]

+ 0 - 57
services/web/frontend/js/ide/editor/directives/aceEditor/cursor-position/CursorPositionAdapter.js

@@ -1,57 +0,0 @@
-/* eslint-disable
-    max-len,
-    no-unused-vars,
-*/
-// TODO: This file was created by bulk-decaffeinate.
-// Fix any style issues and re-enable lint.
-/*
- * decaffeinate suggestions:
- * DS102: Remove unnecessary code created because of implicit returns
- * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
- */
-import EditorShareJsCodec from '../../../EditorShareJsCodec'
-let CursorPositionAdapter
-
-export default CursorPositionAdapter = class CursorPositionAdapter {
-  constructor(editor) {
-    this.editor = editor
-  }
-
-  getCursor() {
-    return this.editor.getCursorPosition()
-  }
-
-  getEditorScrollPosition() {
-    return this.editor.getFirstVisibleRow()
-  }
-
-  setCursor(pos) {
-    pos = pos.cursorPosition || { row: 0, column: 0 }
-    return this.editor.moveCursorToPosition(pos)
-  }
-
-  setEditorScrollPosition(pos) {
-    pos = pos.firstVisibleLine || 0
-    return this.editor.scrollToLine(pos)
-  }
-
-  clearSelection() {
-    return this.editor.selection.clearSelection()
-  }
-
-  gotoLine(line, column) {
-    this.editor.gotoLine(line, column)
-    this.editor.scrollToLine(line, true, true) // centre and animate
-    return this.editor.focus()
-  }
-
-  gotoOffset(offset) {
-    const lines = this.editor.getSession().getDocument().getAllLines()
-    const position = EditorShareJsCodec.shareJsOffsetToRowColumn(offset, lines)
-    return this.gotoLine(position.row + 1, position.column)
-  }
-
-  focus() {
-    this.editor.focus()
-  }
-}

+ 0 - 119
services/web/frontend/js/ide/editor/directives/aceEditor/cursor-position/CursorPositionManager.js

@@ -1,119 +0,0 @@
-/* eslint-disable
-    max-len,
-    no-unused-vars,
-*/
-// TODO: This file was created by bulk-decaffeinate.
-// Fix any style issues and re-enable lint.
-/*
- * decaffeinate suggestions:
- * DS102: Remove unnecessary code created because of implicit returns
- * DS207: Consider shorter variations of null checks
- * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
- */
-let CursorPositionManager
-
-export default CursorPositionManager = class CursorPositionManager {
-  constructor($scope, adapter, localStorage) {
-    this.storePositionAndLine = this.storePositionAndLine.bind(this)
-    this.jumpToPositionInNewDoc = this.jumpToPositionInNewDoc.bind(this)
-    this.onUnload = this.onUnload.bind(this)
-    this.onCursorChange = this.onCursorChange.bind(this)
-    this.onSyncToPdf = this.onSyncToPdf.bind(this)
-    this.$scope = $scope
-    this.adapter = adapter
-    this.localStorage = localStorage
-    this.$scope.$on('editorInit', this.jumpToPositionInNewDoc)
-
-    this.$scope.$on('store-doc-position', this.storePositionAndLine)
-
-    this.$scope.$on('afterChangeDocument', this.jumpToPositionInNewDoc)
-
-    this.$scope.$on('changeEditor', this.storePositionAndLine)
-
-    this.$scope.$on(
-      `${this.$scope.name}:gotoLine`,
-      (e, line, column, syncToPdf) => {
-        if (line != null) {
-          return setTimeout(() => {
-            this.adapter.gotoLine(line, column)
-            if (syncToPdf) this.onSyncToPdf()
-          }, 10)
-        }
-      }
-    ) // Hack: Must happen after @gotoStoredPosition
-
-    this.$scope.$on(`${this.$scope.name}:gotoOffset`, (e, offset) => {
-      if (offset != null) {
-        return setTimeout(() => {
-          return this.adapter.gotoOffset(offset)
-        }, 10)
-      }
-    }) // Hack: Must happen after @gotoStoredPosition
-
-    this.$scope.$on(`${this.$scope.name}:clearSelection`, e => {
-      return this.adapter.clearSelection()
-    })
-  }
-
-  storePositionAndLine() {
-    this.storeCursorPosition()
-    return this.storeFirstVisibleLine()
-  }
-
-  jumpToPositionInNewDoc() {
-    this.doc_id =
-      this.$scope.sharejsDoc != null ? this.$scope.sharejsDoc.doc_id : undefined
-    return setTimeout(() => {
-      return this.gotoStoredPosition()
-    }, 0)
-  }
-
-  onUnload() {
-    this.storeCursorPosition()
-    return this.storeFirstVisibleLine()
-  }
-
-  onCursorChange() {
-    return this.emitCursorUpdateEvent()
-  }
-
-  onSyncToPdf() {
-    return this.$scope.$emit(`cursor:${this.$scope.name}:syncToPdf`)
-  }
-
-  storeFirstVisibleLine() {
-    if (this.doc_id != null) {
-      const docPosition = this.localStorage(`doc.position.${this.doc_id}`) || {}
-      docPosition.firstVisibleLine = this.adapter.getEditorScrollPosition()
-      return this.localStorage(`doc.position.${this.doc_id}`, docPosition)
-    }
-  }
-
-  storeCursorPosition() {
-    if (this.doc_id != null) {
-      const docPosition = this.localStorage(`doc.position.${this.doc_id}`) || {}
-      docPosition.cursorPosition = this.adapter.getCursor()
-      return this.localStorage(`doc.position.${this.doc_id}`, docPosition)
-    }
-  }
-
-  emitCursorUpdateEvent() {
-    const cursor = this.adapter.getCursor()
-    this.$scope.$emit(`cursor:${this.$scope.name}:update`, cursor)
-    window.dispatchEvent(
-      new CustomEvent(`cursor:${this.$scope.name}:update`, {
-        detail: cursor,
-      })
-    )
-  }
-
-  gotoStoredPosition() {
-    if (this.doc_id == null) {
-      return
-    }
-    const pos = this.localStorage(`doc.position.${this.doc_id}`) || {}
-    this.adapter.setCursor(pos)
-    this.adapter.setEditorScrollPosition(pos)
-    this.adapter.focus()
-  }
-}

+ 0 - 396
services/web/frontend/js/ide/editor/directives/aceEditor/highlights/HighlightsManager.js

@@ -1,396 +0,0 @@
-/* eslint-disable
-    camelcase,
-    max-len,
-    no-return-assign,
-    no-unused-vars,
-*/
-// TODO: This file was created by bulk-decaffeinate.
-// Fix any style issues and re-enable lint.
-/*
- * decaffeinate suggestions:
- * DS101: Remove unnecessary use of Array.from
- * DS102: Remove unnecessary code created because of implicit returns
- * DS103: Rewrite code to no longer use __guard__
- * DS205: Consider reworking code to avoid use of IIFEs
- * DS207: Consider shorter variations of null checks
- * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
- */
-import 'ace/ace'
-import ColorManager from '../../../../colors/ColorManager'
-let HighlightsManager
-const { Range } = ace.require('ace/range')
-
-export default HighlightsManager = class HighlightsManager {
-  constructor($scope, editor, element) {
-    this.$scope = $scope
-    this.editor = editor
-    this.element = element
-    this.markerIds = []
-    this.labels = []
-
-    this.$scope.annotationLabel = {
-      show: false,
-      right: 'auto',
-      left: 'auto',
-      top: 'auto',
-      bottom: 'auto',
-      backgroundColor: 'black',
-      text: '',
-    }
-
-    this.$scope.updateLabels = {
-      updatesAbove: 0,
-      updatesBelow: 0,
-    }
-
-    this.$scope.$watch('highlights', value => {
-      return this.redrawAnnotations()
-    })
-
-    this.$scope.$watch('theme', value => {
-      return this.redrawAnnotations()
-    })
-
-    this.editor.on('mousemove', e => {
-      const position = this.editor.renderer.screenToTextCoordinates(
-        e.clientX,
-        e.clientY
-      )
-      e.position = position
-      return this.showAnnotationLabels(position)
-    })
-
-    const onChangeScrollTop = () => {
-      return this.updateShowMoreLabels()
-    }
-
-    this.editor.getSession().on('changeScrollTop', onChangeScrollTop)
-
-    this.$scope.$watch('text', () => {
-      if (this.$scope.navigateHighlights) {
-        return setTimeout(() => {
-          return this.scrollToFirstHighlight()
-        }, 0)
-      }
-    })
-
-    this.editor.on('changeSession', e => {
-      if (e.oldSession != null) {
-        e.oldSession.off('changeScrollTop', onChangeScrollTop)
-      }
-      e.session.on('changeScrollTop', onChangeScrollTop)
-      return this.redrawAnnotations()
-    })
-
-    this.$scope.gotoHighlightBelow = () => {
-      if (this.firstHiddenHighlightAfter == null) {
-        return
-      }
-      return this.editor.scrollToLine(
-        this.firstHiddenHighlightAfter.end.row,
-        true,
-        false
-      )
-    }
-
-    this.$scope.gotoHighlightAbove = () => {
-      if (this.lastHiddenHighlightBefore == null) {
-        return
-      }
-      return this.editor.scrollToLine(
-        this.lastHiddenHighlightBefore.start.row,
-        true,
-        false
-      )
-    }
-  }
-
-  redrawAnnotations() {
-    this._clearMarkers()
-    this._clearLabels()
-
-    for (const annotation of Array.from(this.$scope.highlights || [])) {
-      ;(annotation => {
-        const colorScheme = ColorManager.getColorScheme(
-          annotation.hue,
-          this.element
-        )
-        if (annotation.cursor != null) {
-          this.labels.push({
-            text: annotation.label,
-            range: new Range(
-              annotation.cursor.row,
-              annotation.cursor.column,
-              annotation.cursor.row,
-              annotation.cursor.column + 1
-            ),
-            colorScheme,
-            snapToStartOfRange: true,
-          })
-          return this._drawCursor(annotation, colorScheme)
-        } else if (annotation.highlight != null) {
-          this.labels.push({
-            text: annotation.label,
-            range: new Range(
-              annotation.highlight.start.row,
-              annotation.highlight.start.column,
-              annotation.highlight.end.row,
-              annotation.highlight.end.column
-            ),
-            colorScheme,
-          })
-          return this._drawHighlight(annotation, colorScheme)
-        } else if (annotation.strikeThrough != null) {
-          this.labels.push({
-            text: annotation.label,
-            range: new Range(
-              annotation.strikeThrough.start.row,
-              annotation.strikeThrough.start.column,
-              annotation.strikeThrough.end.row,
-              annotation.strikeThrough.end.column
-            ),
-            colorScheme,
-          })
-          return this._drawStrikeThrough(annotation, colorScheme)
-        }
-      })(annotation)
-    }
-
-    return this.updateShowMoreLabels()
-  }
-
-  showAnnotationLabels(position) {
-    let labelToShow = null
-    for (const label of Array.from(this.labels || [])) {
-      if (label.range.contains(position.row, position.column)) {
-        labelToShow = label
-      }
-    }
-
-    if (labelToShow == null) {
-      // this is the most common path, triggered on mousemove, so
-      // for performance only apply setting when it changes
-      if (
-        __guard__(
-          this.$scope != null ? this.$scope.annotationLabel : undefined,
-          x => x.show
-        ) !== false
-      ) {
-        return this.$scope.$apply(() => {
-          return (this.$scope.annotationLabel.show = false)
-        })
-      }
-    } else {
-      let bottom, coords, left, right, top
-      const $ace = $(this.editor.renderer.container).find('.ace_scroller')
-      // Move the label into the Ace content area so that offsets and positions are easy to calculate.
-      $ace.append(this.element.find('.annotation-label'))
-
-      if (labelToShow.snapToStartOfRange) {
-        coords = this.editor.renderer.textToScreenCoordinates(
-          labelToShow.range.start.row,
-          labelToShow.range.start.column
-        )
-      } else {
-        coords = this.editor.renderer.textToScreenCoordinates(
-          position.row,
-          position.column
-        )
-      }
-
-      const offset = $ace.offset()
-      const height = $ace.height()
-      coords.pageX = coords.pageX - offset.left
-      coords.pageY = coords.pageY - offset.top
-
-      if (coords.pageY > this.editor.renderer.lineHeight * 2) {
-        top = 'auto'
-        bottom = height - coords.pageY
-      } else {
-        top = coords.pageY + this.editor.renderer.lineHeight
-        bottom = 'auto'
-      }
-
-      // Apply this first that the label has the correct width when calculating below
-      this.$scope.$apply(() => {
-        this.$scope.annotationLabel.text = labelToShow.text
-        return (this.$scope.annotationLabel.show = true)
-      })
-
-      const $label = this.element.find('.annotation-label')
-
-      if (coords.pageX + $label.outerWidth() < $ace.width()) {
-        left = coords.pageX
-        right = 'auto'
-      } else {
-        right = 0
-        left = 'auto'
-      }
-
-      return this.$scope.$apply(() => {
-        return (this.$scope.annotationLabel = {
-          show: true,
-          left,
-          right,
-          bottom,
-          top,
-          backgroundColor: labelToShow.colorScheme.labelBackgroundColor,
-          text: labelToShow.text,
-        })
-      })
-    }
-  }
-
-  updateShowMoreLabels() {
-    if (!this.$scope.navigateHighlights) {
-      return
-    }
-    return setTimeout(() => {
-      const firstRow = this.editor.getFirstVisibleRow()
-      const lastRow = this.editor.getLastVisibleRow()
-      let highlightsBefore = 0
-      let highlightsAfter = 0
-      this.lastHiddenHighlightBefore = null
-      this.firstHiddenHighlightAfter = null
-      for (const annotation of Array.from(this.$scope.highlights || [])) {
-        const range = annotation.highlight || annotation.strikeThrough
-        if (range == null) {
-          continue
-        }
-        if (range.start.row < firstRow) {
-          highlightsBefore += 1
-          this.lastHiddenHighlightBefore = range
-        }
-        if (range.end.row > lastRow) {
-          highlightsAfter += 1
-          if (!this.firstHiddenHighlightAfter) {
-            this.firstHiddenHighlightAfter = range
-          }
-        }
-      }
-
-      return this.$scope.$apply(() => {
-        return (this.$scope.updateLabels = {
-          highlightsBefore,
-          highlightsAfter,
-        })
-      })
-    }, 100)
-  }
-
-  scrollToFirstHighlight() {
-    return (() => {
-      const result = []
-      for (const annotation of Array.from(this.$scope.highlights || [])) {
-        const range = annotation.highlight || annotation.strikeThrough
-        if (range == null) {
-          continue
-        }
-        this.editor.scrollToLine(range.start.row, true, false)
-        break
-      }
-      return result
-    })()
-  }
-
-  _clearMarkers() {
-    for (const marker_id of Array.from(this.markerIds)) {
-      this.editor.getSession().removeMarker(marker_id)
-    }
-    return (this.markerIds = [])
-  }
-
-  _clearLabels() {
-    return (this.labels = [])
-  }
-
-  _drawCursor(annotation, colorScheme) {
-    return this._addMarkerWithCustomStyle(
-      new Range(
-        annotation.cursor.row,
-        annotation.cursor.column,
-        annotation.cursor.row,
-        annotation.cursor.column + 1
-      ),
-      'annotation remote-cursor',
-      false,
-      `border-color: ${colorScheme.cursor};`
-    )
-  }
-
-  _drawHighlight(annotation, colorScheme) {
-    return this._addMarkerWithCustomStyle(
-      new Range(
-        annotation.highlight.start.row,
-        annotation.highlight.start.column,
-        annotation.highlight.end.row,
-        annotation.highlight.end.column
-      ),
-      'annotation highlight',
-      false,
-      `background-color: ${colorScheme.highlightBackgroundColor}`
-    )
-  }
-
-  _drawStrikeThrough(annotation, colorScheme) {
-    this._addMarkerWithCustomStyle(
-      new Range(
-        annotation.strikeThrough.start.row,
-        annotation.strikeThrough.start.column,
-        annotation.strikeThrough.end.row,
-        annotation.strikeThrough.end.column
-      ),
-      'annotation strike-through-background',
-      false,
-      `background-color: ${colorScheme.strikeThroughBackgroundColor}`
-    )
-    return this._addMarkerWithCustomStyle(
-      new Range(
-        annotation.strikeThrough.start.row,
-        annotation.strikeThrough.start.column,
-        annotation.strikeThrough.end.row,
-        annotation.strikeThrough.end.column
-      ),
-      'annotation strike-through-foreground',
-      true,
-      `color: ${colorScheme.strikeThroughForegroundColor};`
-    )
-  }
-
-  _addMarkerWithCustomStyle(range, klass, foreground, style) {
-    let markerLayer
-    if (!foreground) {
-      markerLayer = this.editor.renderer.$markerBack
-    } else {
-      markerLayer = this.editor.renderer.$markerFront
-    }
-
-    return this.markerIds.push(
-      this.editor.getSession().addMarker(
-        range,
-        klass,
-        function (html, range, left, top, config) {
-          if (range.isMultiLine()) {
-            return markerLayer.drawTextMarker(html, range, klass, config, style)
-          } else {
-            return markerLayer.drawSingleLineMarker(
-              html,
-              range,
-              `${klass} ace_start`,
-              config,
-              0,
-              style
-            )
-          }
-        },
-        foreground
-      )
-    )
-  }
-}
-
-function __guard__(value, transform) {
-  return typeof value !== 'undefined' && value !== null
-    ? transform(value)
-    : undefined
-}

+ 0 - 100
services/web/frontend/js/ide/editor/directives/aceEditor/metadata/MetadataManager.js

@@ -1,100 +0,0 @@
-import _ from 'lodash'
-/* eslint-disable
-    max-len,
-    no-cond-assign,
-    no-unused-vars,
-*/
-// TODO: This file was created by bulk-decaffeinate.
-// Fix any style issues and re-enable lint.
-/*
- * decaffeinate suggestions:
- * DS102: Remove unnecessary code created because of implicit returns
- * DS207: Consider shorter variations of null checks
- * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
- */
-import 'ace/ace'
-let MetadataManager
-const { Range } = ace.require('ace/range')
-
-const getLastCommandFragment = function (lineUpToCursor) {
-  let m
-  if ((m = lineUpToCursor.match(/(\\[^\\]+)$/))) {
-    return m[1]
-  } else {
-    return null
-  }
-}
-
-export default MetadataManager = class MetadataManager {
-  constructor($scope, editor, element, Metadata) {
-    this.$scope = $scope
-    this.editor = editor
-    this.element = element
-    this.Metadata = Metadata
-    this.debouncer = {} // DocId => Timeout
-
-    const onChange = change => {
-      if (change.origin === 'remote') {
-        return
-      }
-      if (!['remove', 'insert'].includes(change.action)) {
-        return
-      }
-      const cursorPosition = this.editor.getCursorPosition()
-      const { end } = change
-      let range = new Range(end.row, 0, end.row, end.column)
-      let lineUpToCursor = this.editor.getSession().getTextRange(range)
-      if (
-        lineUpToCursor.trim() === '%' ||
-        lineUpToCursor.slice(0, 1) === '\\'
-      ) {
-        range = new Range(end.row, 0, end.row, end.column + 80)
-        lineUpToCursor = this.editor.getSession().getTextRange(range)
-      }
-      const commandFragment = getLastCommandFragment(lineUpToCursor)
-
-      const linesContainPackage = _.some(change.lines, line =>
-        line.match(/^\\usepackage(?:\[.{0,80}?])?{(.{0,80}?)}/)
-      )
-      const linesContainReqPackage = _.some(change.lines, line =>
-        line.match(/^\\RequirePackage(?:\[.{0,80}?])?{(.{0,80}?)}/)
-      )
-      const linesContainLabel = _.some(change.lines, line =>
-        line.match(/\\label{(.{0,80}?)}/)
-      )
-      const linesContainMeta =
-        linesContainPackage || linesContainLabel || linesContainReqPackage
-
-      const lastCommandFragmentIsLabel =
-        (commandFragment != null ? commandFragment.slice(0, 7) : undefined) ===
-        '\\label{'
-      const lastCommandFragmentIsPackage =
-        (commandFragment != null ? commandFragment.slice(0, 11) : undefined) ===
-        '\\usepackage'
-      const lastCommandFragmentIsReqPack =
-        (commandFragment != null ? commandFragment.slice(0, 15) : undefined) ===
-        '\\RequirePackage'
-      const lastCommandFragmentIsMeta =
-        lastCommandFragmentIsPackage ||
-        lastCommandFragmentIsLabel ||
-        lastCommandFragmentIsReqPack
-
-      if (linesContainMeta || lastCommandFragmentIsMeta) {
-        return this.Metadata.scheduleLoadDocMetaFromServer(this.$scope.docId)
-      }
-    }
-
-    this.editor.on('changeSession', e => {
-      e.oldSession.off('change', onChange)
-      return e.session.on('change', onChange)
-    })
-  }
-
-  getAllLabels() {
-    return this.Metadata.getAllLabels()
-  }
-
-  getAllPackages() {
-    return this.Metadata.getAllPackages()
-  }
-}

+ 0 - 137
services/web/frontend/js/ide/editor/directives/aceEditor/spell-check/HighlightedWordManager.js

@@ -1,137 +0,0 @@
-import _ from 'lodash'
-/* eslint-disable
-    max-len,
-    no-return-assign,
-    no-unused-vars,
-*/
-// TODO: This file was created by bulk-decaffeinate.
-// Fix any style issues and re-enable lint.
-/*
- * decaffeinate suggestions:
- * DS102: Remove unnecessary code created because of implicit returns
- * DS207: Consider shorter variations of null checks
- * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
- */
-import 'ace/ace'
-let HighlightedWordManager
-const { Range } = ace.require('ace/range')
-
-class Highlight {
-  constructor(markerId, range, options) {
-    this.markerId = markerId
-    this.range = range
-    this.word = options.word
-    this.suggestions = options.suggestions
-  }
-}
-
-export default HighlightedWordManager = class HighlightedWordManager {
-  constructor(editor) {
-    this.editor = editor
-    this.reset()
-  }
-
-  reset() {
-    if (this.highlights != null) {
-      this.highlights.forEach(highlight => {
-        return this.editor.getSession().removeMarker(highlight.markerId)
-      })
-    }
-    return (this.highlights = [])
-  }
-
-  addHighlight(options) {
-    const session = this.editor.getSession()
-    const doc = session.getDocument()
-    // Set up Range that will automatically update it's positions when the
-    // document changes
-    const range = new Range()
-    range.start = doc.createAnchor({
-      row: options.row,
-      column: options.column,
-    })
-    range.end = doc.createAnchor({
-      row: options.row,
-      column: options.column + options.word.length,
-    })
-    // Prevent range from adding newly typed characters to the end of the word.
-    // This makes it appear as if the spelling error continues to the next word
-    // even after a space
-    range.end.$insertRight = true
-
-    const markerId = session.addMarker(
-      range,
-      'spelling-highlight',
-      'text',
-      false
-    )
-
-    return this.highlights.push(new Highlight(markerId, range, options))
-  }
-
-  removeHighlight(highlight) {
-    this.editor.getSession().removeMarker(highlight.markerId)
-    return (this.highlights = this.highlights.filter(hl => hl !== highlight))
-  }
-
-  removeWord(word) {
-    return this.highlights
-      .filter(highlight => highlight.word === word)
-      .forEach(highlight => {
-        return this.removeHighlight(highlight)
-      })
-  }
-
-  clearRow(row) {
-    return this.highlights
-      .filter(highlight => highlight.range.start.row === row)
-      .forEach(highlight => {
-        return this.removeHighlight(highlight)
-      })
-  }
-
-  findHighlightWithinRange(range) {
-    return _.find(this.highlights, highlight => {
-      return this._doesHighlightOverlapRange(highlight, range.start, range.end)
-    })
-  }
-
-  _doesHighlightOverlapRange(highlight, start, end) {
-    const highlightRow = highlight.range.start.row
-    const highlightStartColumn = highlight.range.start.column
-    const highlightEndColumn = highlight.range.end.column
-
-    const highlightIsAllBeforeRange =
-      highlightRow < start.row ||
-      (highlightRow === start.row && highlightEndColumn <= start.column)
-    const highlightIsAllAfterRange =
-      highlightRow > end.row ||
-      (highlightRow === end.row && highlightStartColumn >= end.column)
-    return !(highlightIsAllBeforeRange || highlightIsAllAfterRange)
-  }
-
-  clearHighlightTouchingRange(range) {
-    const highlight = _.find(this.highlights, hl => {
-      return this._doesHighlightTouchRange(hl, range.start, range.end)
-    })
-    if (highlight) {
-      return this.removeHighlight(highlight)
-    }
-  }
-
-  _doesHighlightTouchRange(highlight, start, end) {
-    const highlightRow = highlight.range.start.row
-    const highlightStartColumn = highlight.range.start.column
-    const highlightEndColumn = highlight.range.end.column
-
-    const rangeStartIsWithinHighlight =
-      highlightStartColumn <= start.column && highlightEndColumn >= start.column
-    const rangeEndIsWithinHighlight =
-      highlightStartColumn <= end.column && highlightEndColumn >= end.column
-
-    return (
-      highlightRow === start.row &&
-      (rangeStartIsWithinHighlight || rangeEndIsWithinHighlight)
-    )
-  }
-}

+ 0 - 22
services/web/frontend/js/ide/editor/directives/aceEditor/spell-check/IgnoredMisspellings.js

@@ -1,22 +0,0 @@
-export const IGNORED_MISSPELLINGS = [
-  'Overleaf',
-  'overleaf',
-  'ShareLaTeX',
-  'sharelatex',
-  'LaTeX',
-  'TeX',
-  'BibTeX',
-  'BibLaTeX',
-  'XeTeX',
-  'XeLaTeX',
-  'LuaTeX',
-  'LuaLaTeX',
-  'http',
-  'https',
-  'www',
-  'COVID',
-  'Lockdown',
-  'lockdown',
-  'Coronavirus',
-  'coronavirus',
-]

+ 0 - 96
services/web/frontend/js/ide/editor/directives/aceEditor/spell-check/SpellCheckAdapter.js

@@ -1,96 +0,0 @@
-import HighlightedWordManager from './HighlightedWordManager'
-import 'ace/ace'
-const { Range } = ace.require('ace/range')
-
-class SpellCheckAdapter {
-  constructor(editor) {
-    this.replaceWord = this.replaceWord.bind(this)
-    this.editor = editor
-    this.highlightedWordManager = new HighlightedWordManager(this.editor)
-  }
-
-  getLines() {
-    return this.editor.getValue().split('\n')
-  }
-
-  getLineCount() {
-    return this.editor.session.getLength()
-  }
-
-  getFirstVisibleRowNum() {
-    return this.editor.renderer.layerConfig.firstRow
-  }
-
-  getLastVisibleRowNum() {
-    return this.editor.renderer.layerConfig.lastRow
-  }
-
-  getLinesByRows(rows) {
-    return rows.map(rowIdx => this.editor.session.doc.getLine(rowIdx))
-  }
-
-  getSelectionContents() {
-    return this.editor.getSelectedText()
-  }
-
-  normalizeChangeEvent(e) {
-    return e
-  }
-
-  getCoordsFromContextMenuEvent(e) {
-    e.domEvent.stopPropagation()
-    return {
-      x: e.domEvent.clientX,
-      y: e.domEvent.clientY,
-    }
-  }
-
-  preventContextMenuEventDefault(e) {
-    e.domEvent.preventDefault()
-  }
-
-  getHighlightFromCoords(coords) {
-    const position = this.editor.renderer.screenToTextCoordinates(
-      coords.x,
-      coords.y
-    )
-    return this.highlightedWordManager.findHighlightWithinRange({
-      start: position,
-      end: position,
-    })
-  }
-
-  isContextMenuEventOnBottomHalf(e) {
-    const { clientY } = e.domEvent
-    const editorBoundingRect = e.target.container.getBoundingClientRect()
-    const relativeYPos =
-      (clientY - editorBoundingRect.top) / editorBoundingRect.height
-    return relativeYPos > 0.5
-  }
-
-  selectHighlightedWord(highlight) {
-    const { row } = highlight.range.start
-    const startColumn = highlight.range.start.column
-    const endColumn = highlight.range.end.column
-
-    this.editor
-      .getSession()
-      .getSelection()
-      .setSelectionRange(new Range(row, startColumn, row, endColumn))
-  }
-
-  replaceWord(highlight, newWord) {
-    const { row } = highlight.range.start
-    const startColumn = highlight.range.start.column
-    const endColumn = highlight.range.end.column
-
-    this.editor
-      .getSession()
-      .replace(new Range(row, startColumn, row, endColumn), newWord)
-
-    // Bring editor back into focus after clicking on suggestion
-    this.editor.focus()
-  }
-}
-
-export default SpellCheckAdapter

Разница между файлами не показана из-за своего большого размера
+ 0 - 24
services/web/frontend/js/ide/editor/directives/aceEditor/spell-check/SpellCheckManager.js


+ 0 - 240
services/web/frontend/js/ide/editor/directives/aceEditor/track-changes/TrackChangesAdapter.js

@@ -1,240 +0,0 @@
-/* eslint-disable
-    camelcase
- */
-import EditorShareJsCodec from '../../../EditorShareJsCodec'
-import 'ace/ace'
-const { Range } = ace.require('ace/range')
-class TrackChangesAdapter {
-  constructor(editor) {
-    this.editor = editor
-    this.changeIdToMarkerIdMap = {}
-  }
-
-  tearDown() {
-    this.changeIdToMarkerIdMap = {}
-  }
-
-  clearAnnotations() {
-    const session = this.editor.getSession()
-    for (const change_id in this.changeIdToMarkerIdMap) {
-      const markers = this.changeIdToMarkerIdMap[change_id]
-      for (const marker_name in markers) {
-        const marker_id = markers[marker_name]
-        session.removeMarker(marker_id)
-      }
-    }
-    this.changeIdToMarkerIdMap = {}
-  }
-
-  onInsertAdded(change) {
-    const start = this.shareJsOffsetToRowColumn(change.op.p)
-    const end = this.shareJsOffsetToRowColumn(change.op.p + change.op.i.length)
-
-    const session = this.editor.getSession()
-    const background_range = new Range(
-      start.row,
-      start.column,
-      end.row,
-      end.column
-    )
-    const background_marker_id = session.addMarker(
-      background_range,
-      'track-changes-marker track-changes-added-marker',
-      'text'
-    )
-    const callout_marker_id = this.createCalloutMarker(
-      start,
-      'track-changes-added-marker-callout'
-    )
-
-    this.changeIdToMarkerIdMap[change.id] = {
-      background_marker_id,
-      callout_marker_id,
-    }
-  }
-
-  onDeleteAdded(change) {
-    const position = this.shareJsOffsetToRowColumn(change.op.p)
-    const session = this.editor.getSession()
-
-    const markerLayer = this.editor.renderer.$markerBack
-    const klass = 'track-changes-marker track-changes-deleted-marker'
-    const background_range = this.makeZeroWidthRange(position)
-    const background_marker_id = session.addMarker(
-      background_range,
-      klass,
-      (html, range, left, top, config) =>
-        markerLayer.drawSingleLineMarker(
-          html,
-          range,
-          `${klass} ace_start`,
-          config,
-          0,
-          ''
-        )
-    )
-
-    const callout_marker_id = this.createCalloutMarker(
-      position,
-      'track-changes-deleted-marker-callout'
-    )
-
-    this.changeIdToMarkerIdMap[change.id] = {
-      background_marker_id,
-      callout_marker_id,
-    }
-  }
-
-  onInsertRemoved(change) {
-    if (this.changeIdToMarkerIdMap[change.id] == null) {
-      return
-    }
-    const { background_marker_id, callout_marker_id } =
-      this.changeIdToMarkerIdMap[change.id]
-    delete this.changeIdToMarkerIdMap[change.id]
-    const session = this.editor.getSession()
-    session.removeMarker(background_marker_id)
-    session.removeMarker(callout_marker_id)
-  }
-
-  onDeleteRemoved(change) {
-    if (this.changeIdToMarkerIdMap[change.id] == null) {
-      return
-    }
-    const { background_marker_id, callout_marker_id } =
-      this.changeIdToMarkerIdMap[change.id]
-    delete this.changeIdToMarkerIdMap[change.id]
-
-    const session = this.editor.getSession()
-    session.removeMarker(background_marker_id)
-    session.removeMarker(callout_marker_id)
-  }
-
-  onChangeMoved(change) {
-    let end
-    const start = this.shareJsOffsetToRowColumn(change.op.p)
-    if (change.op.i != null) {
-      end = this.shareJsOffsetToRowColumn(change.op.p + change.op.i.length)
-    } else {
-      end = start
-    }
-    this.updateMarker(change.id, start, end)
-  }
-
-  onCommentAdded(comment) {
-    if (this.changeIdToMarkerIdMap[comment.id] == null) {
-      // Only create new markers if they don't already exist
-      const start = this.shareJsOffsetToRowColumn(comment.op.p)
-      const end = this.shareJsOffsetToRowColumn(
-        comment.op.p + comment.op.c.length
-      )
-      const session = this.editor.getSession()
-      const background_range = new Range(
-        start.row,
-        start.column,
-        end.row,
-        end.column
-      )
-      const background_marker_id = session.addMarker(
-        background_range,
-        'track-changes-marker track-changes-comment-marker',
-        'text'
-      )
-      const callout_marker_id = this.createCalloutMarker(
-        start,
-        'track-changes-comment-marker-callout'
-      )
-      this.changeIdToMarkerIdMap[comment.id] = {
-        background_marker_id,
-        callout_marker_id,
-      }
-    }
-  }
-
-  onCommentMoved(comment) {
-    const start = this.shareJsOffsetToRowColumn(comment.op.p)
-    const end = this.shareJsOffsetToRowColumn(
-      comment.op.p + comment.op.c.length
-    )
-    this.updateMarker(comment.id, start, end)
-  }
-
-  onCommentRemoved(comment) {
-    if (this.changeIdToMarkerIdMap[comment.id] != null) {
-      // Resolved comments may not have marker ids
-      const { background_marker_id, callout_marker_id } =
-        this.changeIdToMarkerIdMap[comment.id]
-      delete this.changeIdToMarkerIdMap[comment.id]
-      const session = this.editor.getSession()
-      session.removeMarker(background_marker_id)
-      session.removeMarker(callout_marker_id)
-    }
-  }
-
-  updateMarker(change_id, start, end) {
-    if (this.changeIdToMarkerIdMap[change_id] == null) {
-      return
-    }
-    const session = this.editor.getSession()
-    const markers = session.getMarkers()
-    const { background_marker_id, callout_marker_id } =
-      this.changeIdToMarkerIdMap[change_id]
-    if (background_marker_id != null && markers[background_marker_id] != null) {
-      const background_marker = markers[background_marker_id]
-      background_marker.range.start = start
-      background_marker.range.end = end
-    }
-    if (callout_marker_id != null && markers[callout_marker_id] != null) {
-      const callout_marker = markers[callout_marker_id]
-      callout_marker.range.start = start
-      callout_marker.range.end = start
-    }
-  }
-
-  shareJsOffsetToRowColumn(offset) {
-    const lines = this.editor.getSession().getDocument().getAllLines()
-    return EditorShareJsCodec.shareJsOffsetToRowColumn(offset, lines)
-  }
-
-  createCalloutMarker(position, klass) {
-    const session = this.editor.getSession()
-    const callout_range = this.makeZeroWidthRange(position)
-    const markerLayer = this.editor.renderer.$markerBack
-    return session.addMarker(
-      callout_range,
-      klass,
-      (html, range, left, top, config) =>
-        markerLayer.drawSingleLineMarker(
-          html,
-          range,
-          `track-changes-marker-callout ${klass} ace_start`,
-          config,
-          0,
-          'width: auto; right: 0;'
-        )
-    )
-  }
-
-  makeZeroWidthRange(position) {
-    const ace_range = new Range(
-      position.row,
-      position.column,
-      position.row,
-      position.column
-    )
-    // Our delete marker is zero characters wide, but Ace doesn't draw ranges
-    // that are empty. So we monkey patch the range to tell Ace it's not empty
-    // We do want to claim to be empty if we're off screen after clipping rows
-    //  though. This is the code we need to trick:
-    //   var range = marker.range.clipRows(config.firstRow, config.lastRow);
-    //   if (range.isEmpty()) continue;
-    ace_range.clipRows = function (first_row, last_row) {
-      this.isEmpty = function () {
-        return first_row > this.end.row || last_row < this.start.row
-      }
-      return this
-    }
-    return ace_range
-  }
-}
-export default TrackChangesAdapter

+ 0 - 664
services/web/frontend/js/ide/editor/directives/aceEditor/track-changes/TrackChangesManager.js

@@ -1,664 +0,0 @@
-/* eslint-disable
-    camelcase,
-    max-len
- */
-import EditorShareJsCodec from '../../../EditorShareJsCodec'
-import 'ace/ace'
-import '../../../../../utils/EventEmitter'
-import '../../../../colors/ColorManager'
-import { debugConsole } from '@/utils/debugging'
-const { Range } = ace.require('ace/range')
-class TrackChangesManager {
-  constructor($scope, editor, element, adapter) {
-    this._doneUpdateThisLoop = false
-    this._pendingUpdates = false
-
-    this.onChangeSession = this.onChangeSession.bind(this)
-    this.onChangeSelection = this.onChangeSelection.bind(this)
-    this.onCut = this.onCut.bind(this)
-    this.onPaste = this.onPaste.bind(this)
-    this.onResize = this.onResize.bind(this)
-    this.tearDown = this.tearDown.bind(this)
-
-    this.$scope = $scope
-    this.editor = editor
-    this.element = element
-    this.adapter = adapter
-    this._scrollTimeout = null
-    this.changingSelection = false
-
-    if (window.trackChangesManager == null) {
-      window.trackChangesManager = this
-    }
-
-    this.$scope.$watch('trackChanges', track_changes => {
-      if (track_changes == null) {
-        return
-      }
-      this.setTrackChanges(track_changes)
-    })
-
-    this.$scope.$watch('sharejsDoc', (doc, oldDoc) => {
-      if (doc == null) {
-        return
-      }
-      if (oldDoc != null) {
-        this.disconnectFromDoc(oldDoc)
-      }
-      this.setTrackChanges(this.$scope.trackChanges)
-      this.connectToDoc(doc)
-    })
-
-    this.$scope.$on('comment:add', (e, thread_id, offset, length) => {
-      this.addCommentToSelection(thread_id, offset, length)
-    })
-
-    this.$scope.$on('comment:select_line', e => {
-      this.selectLineIfNoSelection()
-    })
-
-    this.$scope.$on('changes:accept', (e, change_ids) => {
-      this.acceptChangeIds(change_ids)
-    })
-
-    this.$scope.$on('changes:reject', (e, change_ids) => {
-      this.rejectChangeIds(change_ids)
-    })
-
-    this.$scope.$on('comment:remove', (e, comment_id) => {
-      this.removeCommentId(comment_id)
-    })
-
-    this.$scope.$on('comment:resolve_threads', (e, thread_ids) => {
-      this.hideCommentsByThreadIds(thread_ids)
-    })
-
-    this.$scope.$on('comment:unresolve_thread', (e, thread_id) => {
-      this.showCommentByThreadId(thread_id)
-    })
-
-    this.$scope.$on('review-panel:recalculate-screen-positions', () => {
-      this.recalculateReviewEntriesScreenPositions()
-    })
-
-    this._resetCutState()
-  }
-
-  onChangeSession(e) {
-    this.clearAnnotations()
-    this.redrawAnnotations()
-
-    if (this.editor) {
-      this.editor.session.on('changeScrollTop', this.onChangeScroll.bind(this))
-    }
-  }
-
-  onChangeScroll() {
-    if (this._scrollTimeout == null) {
-      return (this._scrollTimeout = setTimeout(() => {
-        this.recalculateVisibleEntries()
-        this.$scope.$apply()
-        return (this._scrollTimeout = null)
-      }, 200))
-    }
-  }
-
-  onChangeSelection() {
-    // Deletes can send about 5 changeSelection events, so
-    // just act on the last one.
-    if (!this.changingSelection) {
-      this.changingSelection = true
-      return this.$scope.$evalAsync(() => {
-        this.changingSelection = false
-        return this.updateFocus()
-      })
-    }
-  }
-
-  onResize() {
-    this.recalculateReviewEntriesScreenPositions()
-  }
-
-  connectToDoc(doc) {
-    this.rangesTracker = doc.ranges
-    this.setTrackChanges(this.$scope.trackChanges)
-
-    doc.on('ranges:dirty', () => {
-      this.updateAnnotations()
-    })
-    doc.on('ranges:clear', () => {
-      this.clearAnnotations()
-    })
-    doc.on('ranges:redraw', () => {
-      this.redrawAnnotations()
-    })
-  }
-
-  disconnectFromDoc(doc) {
-    doc.off('ranges:clear')
-    doc.off('ranges:redraw')
-    doc.off('ranges:dirty')
-  }
-
-  tearDown() {
-    this.adapter.tearDown()
-  }
-
-  setTrackChanges(value) {
-    if (value) {
-      if (this.$scope.sharejsDoc != null) {
-        this.$scope.sharejsDoc.track_changes_as = window.user.id || 'anonymous'
-      }
-    } else {
-      if (this.$scope.sharejsDoc != null) {
-        this.$scope.sharejsDoc.track_changes_as = null
-      }
-    }
-  }
-
-  clearAnnotations() {
-    this.adapter.clearAnnotations()
-  }
-
-  redrawAnnotations() {
-    for (const change of Array.from(this.rangesTracker.changes)) {
-      if (change.op.i != null) {
-        this.adapter.onInsertAdded(change)
-      } else if (change.op.d != null) {
-        this.adapter.onDeleteAdded(change)
-      }
-    }
-
-    Array.from(this.rangesTracker.comments).forEach(comment => {
-      if (!this.isCommentResolved(comment)) {
-        this.adapter.onCommentAdded(comment)
-      }
-    })
-
-    this.broadcastChange()
-  }
-
-  updateAnnotations() {
-    // Doc updates with multiple ops, like search/replace or block comments
-    // will call this with every individual op in a single event loop. So only
-    // do the first this loop, then schedule an update for the next loop for
-    // the rest.
-    if (!this._doneUpdateThisLoop) {
-      this._doUpdateAnnotations()
-      this._doneUpdateThisLoop = true
-      return setTimeout(() => {
-        if (this._pendingUpdates) {
-          this._doUpdateAnnotations()
-        }
-        this._doneUpdateThisLoop = false
-        this._pendingUpdates = false
-      })
-    } else {
-      this._pendingUpdates = true
-    }
-  }
-
-  _doUpdateAnnotations() {
-    let change, comment
-    const dirty = this.rangesTracker.getDirtyState()
-
-    let updateMarkers = false
-
-    for (const id in dirty.change.added) {
-      change = dirty.change.added[id]
-      if (change.op.i != null) {
-        this.adapter.onInsertAdded(change)
-      } else if (change.op.d != null) {
-        this.adapter.onDeleteAdded(change)
-      }
-    }
-    for (const id in dirty.change.removed) {
-      change = dirty.change.removed[id]
-      if (change.op.i != null) {
-        this.adapter.onInsertRemoved(change)
-      } else if (change.op.d != null) {
-        this.adapter.onDeleteRemoved(change)
-      }
-    }
-    for (const id in dirty.change.moved) {
-      change = dirty.change.moved[id]
-      updateMarkers = true
-      this.adapter.onChangeMoved(change)
-    }
-
-    for (const id in dirty.comment.added) {
-      comment = dirty.comment.added[id]
-      if (!this.isCommentResolved(comment)) {
-        this.adapter.onCommentAdded(comment)
-      }
-    }
-    for (const id in dirty.comment.removed) {
-      comment = dirty.comment.removed[id]
-      if (!this.isCommentResolved(comment)) {
-        this.adapter.onCommentRemoved(comment)
-      }
-    }
-    for (const id in dirty.comment.moved) {
-      comment = dirty.comment.moved[id]
-      if (this.adapter.onCommentMoved && !this.isCommentResolved(comment)) {
-        updateMarkers = true
-        this.adapter.onCommentMoved(comment)
-      }
-    }
-
-    /**
-     * For now, if not using ACE don't worry about the markers
-     */
-    if (!this.editor) {
-      updateMarkers = false
-    }
-
-    this.rangesTracker.resetDirtyState()
-    if (updateMarkers) {
-      this.editor.renderer.updateBackMarkers()
-    }
-    this.broadcastChange()
-  }
-
-  addComment(offset, content, thread_id) {
-    const op = { c: content, p: offset, t: thread_id }
-    // @rangesTracker.applyOp op # Will apply via sharejs
-    this.$scope.sharejsDoc.submitOp(op)
-  }
-
-  addCommentToSelection(thread_id, offset, length) {
-    const start = this.adapter.shareJsOffsetToRowColumn(offset)
-    const end = this.adapter.shareJsOffsetToRowColumn(offset + length)
-    const range = new Range(start.row, start.column, end.row, end.column)
-    const content = this.editor.session.getTextRange(range)
-    this.addComment(offset, content, thread_id)
-  }
-
-  isCommentResolved(comment) {
-    return this.rangesTracker.resolvedThreadIds[comment.op.t]
-  }
-
-  selectLineIfNoSelection() {
-    if (this.editor.selection.isEmpty()) {
-      this.editor.selection.selectLine()
-    }
-  }
-
-  acceptChangeIds(change_ids) {
-    this.rangesTracker.removeChangeIds(change_ids)
-    this.updateAnnotations()
-    this.updateFocus()
-  }
-
-  rejectChangeIds(change_ids) {
-    const changes = this.rangesTracker.getChanges(change_ids)
-    if (changes.length === 0) {
-      return
-    }
-
-    // When doing bulk rejections, adjacent changes might interact with each other.
-    // Consider an insertion with an adjacent deletion (which is a common use-case, replacing words):
-    //
-    //     "foo bar baz" -> "foo quux baz"
-    //
-    // The change above will be modeled with two ops, with the insertion going first:
-    //
-    //     foo quux baz
-    //         |--| -> insertion of "quux", op 1, at position 4
-    //             | -> deletion of "bar", op 2, pushed forward by "quux" to position 8
-    //
-    // When rejecting these changes at once, if the insertion is rejected first, we get unexpected
-    // results. What happens is:
-    //
-    //     1) Rejecting the insertion deletes the added word "quux", i.e., it removes 4 chars
-    //        starting from position 4;
-    //
-    //           "foo quux baz" -> "foo  baz"
-    //                |--| -> 4 characters to be removed
-    //
-    //     2) Rejecting the deletion adds the deleted word "bar" at position 8 (i.e. it will act as if
-    //        the word "quuux" was still present).
-    //
-    //            "foo  baz" -> "foo  bazbar"
-    //                     | -> deletion of "bar" is reverted by reinserting "bar" at position 8
-    //
-    // While the intended result would be "foo bar baz", what we get is:
-    //
-    //      "foo  bazbar" (note "bar" readded at position 8)
-    //
-    // The issue happens because of step 1. To revert the insertion of "quux", 4 characters are deleted
-    // from position 4. This includes the position where the deletion exists; when that position is
-    // cleared, the RangesTracker considers that the deletion is gone and stops tracking/updating it.
-    // As we still hold a reference to it, the code tries to revert it by readding the deleted text, but
-    // does so at the outdated position (position 8, which was valid when "quux" was present).
-    //
-    // To avoid this kind of problem, we need to make sure that reverting operations doesn't affect
-    // subsequent operations that come after. Reverse sorting the operations based on position will
-    // achieve it; in the case above, it makes sure that the the deletion is reverted first:
-    //
-    //     1) Rejecting the deletion adds the deleted word "bar" at position 8
-    //
-    //            "foo quux baz" -> "foo quuxbar baz"
-    //                                       | -> deletion of "bar" is reverted by
-    //                                            reinserting "bar" at position 8
-    //
-    //     2) Rejecting the insertion deletes the added word "quux", i.e., it removes 4 chars
-    //        starting from position 4 and achieves the expected result:
-    //
-    //           "foo quuxbar baz" -> "foo bar baz"
-    //                |--| -> 4 characters to be removed
-
-    changes.sort((a, b) => b.op.p - a.op.p)
-
-    const session = this.editor.getSession()
-    for (const change of Array.from(changes)) {
-      if (change.op.d != null) {
-        const content = change.op.d
-        const position = this.adapter.shareJsOffsetToRowColumn(change.op.p)
-        session.$fromReject = true // Tell track changes to cancel out delete
-        session.insert(position, content)
-        session.$fromReject = false
-      } else if (change.op.i != null) {
-        const start = this.adapter.shareJsOffsetToRowColumn(change.op.p)
-        const end = this.adapter.shareJsOffsetToRowColumn(
-          change.op.p + change.op.i.length
-        )
-        const editor_text = session.getDocument().getTextRange({ start, end })
-        if (editor_text !== change.op.i) {
-          throw new Error(
-            `Op to be removed (${JSON.stringify(
-              change.op
-            )}), does not match editor text, '${editor_text}'`
-          )
-        }
-        session.$fromReject = true
-        session.remove({ start, end })
-        session.$fromReject = false
-      } else {
-        throw new Error(`unknown change: ${JSON.stringify(change)}`)
-      }
-    }
-    setTimeout(() => this.updateFocus())
-  }
-
-  removeCommentId(comment_id) {
-    this.rangesTracker.removeCommentId(comment_id)
-    return this.updateAnnotations()
-  }
-
-  hideCommentsByThreadIds(thread_ids) {
-    const resolve_ids = {}
-    const comments = this.rangesTracker.comments || []
-    for (const id of Array.from(thread_ids)) {
-      resolve_ids[id] = true
-    }
-
-    for (const comment of comments) {
-      if (resolve_ids[comment.op.t]) {
-        this.adapter.onCommentRemoved(comment)
-      }
-    }
-    return this.broadcastChange()
-  }
-
-  showCommentByThreadId(thread_id) {
-    const comments = this.rangesTracker.comments || []
-    for (const comment of comments) {
-      if (comment.op.t === thread_id && !this.isCommentResolved(comment)) {
-        this.adapter.onCommentAdded(comment)
-      }
-    }
-    return this.broadcastChange()
-  }
-
-  _resetCutState() {
-    return (this._cutState = {
-      text: null,
-      comments: [],
-      docId: null,
-    })
-  }
-
-  onCut() {
-    this._resetCutState()
-    const selection = this.editor.getSelectionRange()
-    const selection_start = this._rangeToShareJs(selection.start)
-    const selection_end = this._rangeToShareJs(selection.end)
-    this._cutState.text = this.editor.getSelectedText()
-    this._cutState.docId = this.$scope.docId
-    return (() => {
-      const result = []
-      for (const comment of Array.from(this.rangesTracker.comments)) {
-        const comment_start = comment.op.p
-        const comment_end = comment_start + comment.op.c.length
-        if (selection_start <= comment_start && comment_end <= selection_end) {
-          result.push(
-            this._cutState.comments.push({
-              offset: comment.op.p - selection_start,
-              text: comment.op.c,
-              comment,
-            })
-          )
-        } else {
-          result.push(undefined)
-        }
-      }
-      return result
-    })()
-  }
-
-  onPaste() {
-    this.editor.once('change', change => {
-      if (change.action !== 'insert') {
-        return
-      }
-      const pasted_text = change.lines.join('\n')
-      const paste_offset = this._rangeToShareJs(change.start)
-      // We have to wait until the change has been processed by the range
-      // tracker, since if we move the ops into place beforehand, they will be
-      // moved again when the changes are processed by the range tracker. This
-      // ranges:dirty event is fired after the doc has applied the changes to
-      // the range tracker.
-      this.$scope.sharejsDoc.on('ranges:dirty.paste', () => {
-        // Doc event emitter uses namespaced events
-        this.$scope.sharejsDoc.off('ranges:dirty.paste')
-        if (
-          pasted_text === this._cutState.text &&
-          this.$scope.docId === this._cutState.docId
-        ) {
-          for (const { comment, offset, text } of Array.from(
-            this._cutState.comments
-          )) {
-            const op = { c: text, p: paste_offset + offset, t: comment.id }
-            this.$scope.sharejsDoc.submitOp(op)
-          } // Resubmitting an existing comment op (by thread id) will move it
-        }
-        this._resetCutState()
-        // Check that comments still match text. Will throw error if not.
-        this.rangesTracker.validate(this.editor.getValue())
-      })
-    })
-  }
-
-  checkMapping() {
-    // TODO: reintroduce this check
-    let background_marker_id, callout_marker_id, end, marker, op, start
-    const session = this.editor.getSession()
-
-    // Make a copy of session.getMarkers() so we can modify it
-    const markers = {}
-    const object = session.getMarkers()
-    for (const marker_id in object) {
-      marker = object[marker_id]
-      markers[marker_id] = marker
-    }
-
-    const expected_markers = []
-    for (const change of Array.from(this.rangesTracker.changes)) {
-      if (this.adapter.changeIdToMarkerIdMap[change.id] != null) {
-        ;({ op } = change)
-        ;({ background_marker_id, callout_marker_id } =
-          this.adapter.changeIdToMarkerIdMap[change.id])
-        start = this.adapter.shareJsOffsetToRowColumn(op.p)
-        if (op.i != null) {
-          end = this.adapter.shareJsOffsetToRowColumn(op.p + op.i.length)
-        } else if (op.d != null) {
-          end = start
-        }
-        expected_markers.push({
-          marker_id: background_marker_id,
-          start,
-          end,
-        })
-        expected_markers.push({
-          marker_id: callout_marker_id,
-          start,
-          end: start,
-        })
-      }
-    }
-
-    for (const comment of Array.from(this.rangesTracker.comments)) {
-      if (this.adapter.changeIdToMarkerIdMap[comment.id] != null) {
-        ;({ background_marker_id, callout_marker_id } =
-          this.adapter.changeIdToMarkerIdMap[comment.id])
-        start = this.adapter.shareJsOffsetToRowColumn(comment.op.p)
-        end = this.adapter.shareJsOffsetToRowColumn(
-          comment.op.p + comment.op.c.length
-        )
-        expected_markers.push({
-          marker_id: background_marker_id,
-          start,
-          end,
-        })
-        expected_markers.push({
-          marker_id: callout_marker_id,
-          start,
-          end: start,
-        })
-      }
-    }
-
-    for (const { marker_id, start, end } of Array.from(expected_markers)) {
-      marker = markers[marker_id]
-      delete markers[marker_id]
-      if (
-        marker.range.start.row !== start.row ||
-        marker.range.start.column !== start.column ||
-        marker.range.end.row !== end.row ||
-        marker.range.end.column !== end.column
-      ) {
-        debugConsole.error("Change doesn't match marker anymore", {
-          marker,
-          start,
-          end,
-        })
-      }
-    }
-
-    return (() => {
-      const result = []
-      for (const marker_id in markers) {
-        marker = markers[marker_id]
-        if (/track-changes/.test(marker.clazz)) {
-          result.push(debugConsole.error('Orphaned ace marker', marker))
-        } else {
-          result.push(undefined)
-        }
-      }
-      return result
-    })()
-  }
-
-  broadcastChange() {
-    this.$scope.$emit('editor:track-changes:changed', this.$scope.docId)
-  }
-
-  recalculateReviewEntriesScreenPositions() {
-    if (!this.editor) {
-      return
-    }
-    const session = this.editor.getSession()
-    const { renderer } = this.editor
-    const entries = this._getCurrentDocEntries()
-    const object = entries || {}
-    for (const entry_id in object) {
-      const entry = object[entry_id]
-      const doc_position = this.adapter.shareJsOffsetToRowColumn(entry.offset)
-      const screen_position = session.documentToScreenPosition(
-        doc_position.row,
-        doc_position.column
-      )
-      const y = screen_position.row * renderer.lineHeight
-      if (entry.screenPos == null) {
-        entry.screenPos = {}
-      }
-      entry.screenPos.y = y
-      entry.docPos = doc_position
-    }
-    this.recalculateVisibleEntries()
-    this.$scope.$apply()
-  }
-
-  recalculateVisibleEntries() {
-    const OFFSCREEN_ROWS = 20
-
-    // With less than this number of entries, don't bother culling to avoid
-    // little UI jumps when scrolling.
-    const CULL_AFTER = 100
-
-    const { firstRow, lastRow } = this.editor.renderer.layerConfig
-    const entries = this._getCurrentDocEntries() || {}
-    const entriesLength = Object.keys(entries).length
-    let changed = false
-    for (const entry_id in entries) {
-      const entry = entries[entry_id]
-      const old = entry.visible
-      entry.visible =
-        entriesLength < CULL_AFTER ||
-        (firstRow - OFFSCREEN_ROWS <= entry.docPos.row &&
-          entry.docPos.row <= lastRow + OFFSCREEN_ROWS)
-      if (entry.visible !== old) {
-        changed = true
-      }
-    }
-    if (changed) {
-      this.$scope.$emit('editor:track-changes:visibility_changed')
-    }
-  }
-
-  _getCurrentDocEntries() {
-    const doc_id = this.$scope.docId
-    const entries = this.$scope.reviewPanel.entries[doc_id] || {}
-    return entries
-  }
-
-  updateFocus() {
-    if (this.editor) {
-      const selection = this.editor.getSelectionRange()
-      const selection_start = this._rangeToShareJs(selection.start)
-      const selection_end = this._rangeToShareJs(selection.end)
-      const is_selection = selection_start !== selection_end
-      this.$scope.$emit(
-        'editor:focus:changed',
-        selection_start,
-        selection_end,
-        is_selection
-      )
-    }
-  }
-
-  _rangeToShareJs(range) {
-    const lines = this.editor.getSession().getDocument().getLines(0, range.row)
-    return EditorShareJsCodec.rangeToShareJs(range, lines)
-  }
-
-  _changeToShareJs(delta) {
-    const lines = this.editor
-      .getSession()
-      .getDocument()
-      .getLines(0, delta.start.row)
-    return EditorShareJsCodec.changeToShareJs(delta, lines)
-  }
-}
-export default TrackChangesManager

+ 0 - 30
services/web/frontend/js/ide/editor/directives/aceEditor/undo/UndoManager.js

@@ -1,30 +0,0 @@
-import 'ace/ace'
-const BuiltInUndoManager = ace.require('ace/undomanager').UndoManager
-
-class UndoManager {
-  constructor(editor) {
-    this.editor = editor
-
-    this.onChangeSession = this.onChangeSession.bind(this)
-    this.onChange = this.onChange.bind(this)
-  }
-
-  onChangeSession(session) {
-    session.setUndoManager(new BuiltInUndoManager())
-  }
-
-  onChange(change) {
-    if (change.origin !== 'remote') return
-
-    // HACK: remote changes in Ace are added by the ShareJS/Ace adapter
-    // asynchronously via a timeout (see attach_ace function). This makes it
-    // impossible to clear to undo stack when remote changes are received.
-    // To hack around this we queue the undo stack clear so that it applies
-    // after the change is applied
-    setTimeout(() => {
-      this.editor.getSession().getUndoManager().reset()
-    })
-  }
-}
-
-export default UndoManager

+ 0 - 25
services/web/frontend/js/ide/files/services/files.js

@@ -1,25 +0,0 @@
-import _ from 'lodash'
-import App from '../../../base'
-
-export default App.factory('files', [
-  'ide',
-  function (ide) {
-    const Files = {
-      getTeXFiles() {
-        const texFiles = []
-        ide.fileTreeManager.forEachEntity(function (entity, _folder, path) {
-          if (
-            entity.type === 'doc' &&
-            /.*\.(tex|md|txt|tikz)/.test(entity.name)
-          ) {
-            const cloned = _.clone(entity)
-            cloned.path = path
-            texFiles.push(cloned)
-          }
-        })
-        return texFiles
-      },
-    }
-    return Files
-  },
-])

+ 0 - 54
services/web/frontend/js/ide/graphics/services/graphics.js

@@ -1,54 +0,0 @@
-import _ from 'lodash'
-/* eslint-disable
-    max-len,
-*/
-// TODO: This file was created by bulk-decaffeinate.
-// Fix any style issues and re-enable lint.
-/*
- * decaffeinate suggestions:
- * DS102: Remove unnecessary code created because of implicit returns
- * DS103: Rewrite code to no longer use __guard__
- * DS207: Consider shorter variations of null checks
- * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
- */
-import App from '../../../base'
-
-export default App.factory('graphics', [
-  'ide',
-  function (ide) {
-    const Graphics = {
-      getGraphicsFiles() {
-        const graphicsFiles = []
-        ide.fileTreeManager.forEachEntity(function (entity, folder, path) {
-          if (
-            entity.type === 'file' &&
-            __guardMethod__(
-              entity != null ? entity.name : undefined,
-              'match',
-              o => o.match(/.*\.(png|jpg|jpeg|pdf|eps)/i)
-            )
-          ) {
-            const cloned = _.clone(entity)
-            cloned.path = path
-            return graphicsFiles.push(cloned)
-          }
-        })
-        return graphicsFiles
-      },
-    }
-
-    return Graphics
-  },
-])
-
-function __guardMethod__(obj, methodName, transform) {
-  if (
-    typeof obj !== 'undefined' &&
-    obj !== null &&
-    typeof obj[methodName] === 'function'
-  ) {
-    return transform(obj, methodName)
-  } else {
-    return undefined
-  }
-}

+ 0 - 44
services/web/frontend/js/ide/preamble/services/preamble.js

@@ -1,44 +0,0 @@
-/* eslint-disable
-    max-len,
-*/
-// TODO: This file was created by bulk-decaffeinate.
-// Fix any style issues and re-enable lint.
-/*
- * decaffeinate suggestions:
- * DS102: Remove unnecessary code created because of implicit returns
- * DS103: Rewrite code to no longer use __guard__
- * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
- */
-import App from '../../../base'
-
-export default App.factory('preamble', function (ide) {
-  const Preamble = {
-    getPreambleText() {
-      const text = ide.editorManager.getCurrentDocValue().slice(0, 5000)
-      const preamble =
-        __guard__(text.match(/([^]*)^\\begin\{document\}/m), x => x[1]) || ''
-      return preamble
-    },
-
-    getGraphicsPaths() {
-      let match
-      const preamble = Preamble.getPreambleText()
-      const graphicsPathsArgs =
-        __guard__(preamble.match(/\\graphicspath\{(.*)\}/), x => x[1]) || ''
-      const paths = []
-      const re = /\{([^}]*)\}/g
-      while ((match = re.exec(graphicsPathsArgs))) {
-        paths.push(match[1])
-      }
-      return paths
-    },
-  }
-
-  return Preamble
-})
-
-function __guard__(value, transform) {
-  return typeof value !== 'undefined' && value !== null
-    ? transform(value)
-    : undefined
-}

+ 0 - 21
services/web/frontend/js/ide/review-panel/controllers/ReviewPanelController.js

@@ -280,26 +280,6 @@ export default App.controller('ReviewPanelController', [
       return rangesTrackers[doc_id]
       return rangesTrackers[doc_id]
     }
     }
 
 
-    let scrollbar = {}
-    ide.$scope.reviewPanelEventsBridge.on(
-      'aceScrollbarVisibilityChanged',
-      function (isVisible, scrollbarWidth) {
-        scrollbar = { isVisible, scrollbarWidth }
-        return updateScrollbar()
-      }
-    )
-
-    function updateScrollbar() {
-      if (
-        scrollbar.isVisible &&
-        ide.$scope.reviewPanel.subView === $scope.SubViews.CUR_FILE
-      ) {
-        return $reviewPanelEl.css('right', `${scrollbar.scrollbarWidth}px`)
-      } else {
-        return $reviewPanelEl.css('right', '0')
-      }
-    }
-
     $scope.$watch(
     $scope.$watch(
       '!ui.reviewPanelOpen && reviewPanel.hasEntries',
       '!ui.reviewPanelOpen && reviewPanel.hasEntries',
       function (open, prevVal) {
       function (open, prevVal) {
@@ -335,7 +315,6 @@ export default App.controller('ReviewPanelController', [
       if (view == null) {
       if (view == null) {
         return
         return
       }
       }
-      updateScrollbar()
       if (view === $scope.SubViews.OVERVIEW) {
       if (view === $scope.SubViews.OVERVIEW) {
         return refreshOverviewPanel()
         return refreshOverviewPanel()
       } else if (oldView === $scope.SubViews.OVERVIEW) {
       } else if (oldView === $scope.SubViews.OVERVIEW) {

+ 8 - 18
services/web/frontend/js/ide/review-panel/directives/reviewPanelSorted.js

@@ -249,16 +249,14 @@ export default App.directive('reviewPanelSorted', function () {
       // mousewheel/trackpad scrolling behaviour from Ace, and turn mousewheel events into
       // mousewheel/trackpad scrolling behaviour from Ace, and turn mousewheel events into
       // scroll events ourselves, then it makes the review panel slightly less smooth (barely)
       // scroll events ourselves, then it makes the review panel slightly less smooth (barely)
       // noticeable, but keeps it perfectly in step with Ace.
       // noticeable, but keeps it perfectly in step with Ace.
-      ace
-        .require('ace/lib/event')
-        .addMouseWheelListener(scroller[0], function (e) {
-          const deltaY = e.wheelY
-          const old_top = parseInt(list.css('top'))
-          const top = old_top - deltaY * 4
-          scrollAce(-top)
-          dispatchScrollEvent(deltaY * 4)
-          return e.preventDefault()
-        })
+      scroller[0].addEventListener('wheel', e => {
+        // FIXME (or remove this): https://developer.mozilla.org/en-US/docs/Web/API/Element/wheel_event
+        const deltaY = e.wheelY
+        const old_top = parseInt(list.css('top'))
+        const top = old_top - deltaY * 4
+        dispatchScrollEvent(deltaY * 4)
+        return e.preventDefault()
+      })
 
 
       // We always scroll by telling Ace to scroll and then updating the
       // We always scroll by telling Ace to scroll and then updating the
       // review panel. This lets Ace manage the size of the scroller and
       // review panel. This lets Ace manage the size of the scroller and
@@ -274,14 +272,6 @@ export default App.directive('reviewPanelSorted', function () {
         }
         }
       }
       }
 
 
-      const scrollAce = scrollTop =>
-        scope.reviewPanelEventsBridge.emit('externalScroll', scrollTop)
-
-      scope.reviewPanelEventsBridge.on('aceScroll', scrollPanel)
-      scope.$on('$destroy', () =>
-        scope.reviewPanelEventsBridge.off('aceScroll')
-      )
-
       // receive the scroll position from the CodeMirror 6 track changes extension
       // receive the scroll position from the CodeMirror 6 track changes extension
       window.addEventListener('editor:scroll', event => {
       window.addEventListener('editor:scroll', event => {
         const { scrollTop, height, paddingTop } = event.detail
         const { scrollTop, height, paddingTop } = event.detail

+ 0 - 7
services/web/frontend/js/ide/toolbar/EditorLoaderController.js

@@ -10,12 +10,5 @@ App.controller('EditorLoaderController', [
         val === true ? 'rich-text' : 'source'
         val === true ? 'rich-text' : 'source'
       )
       )
     })
     })
-
-    $scope.$watch('editor.newSourceEditor', function (val) {
-      localStorage(
-        `editor.source_editor.${$scope.project_id}`,
-        val === true ? 'cm6' : 'ace'
-      )
-    })
   },
   },
 ])
 ])

+ 0 - 23
services/web/frontend/js/ide/toolbar/EditorToolbarController.js

@@ -1,23 +0,0 @@
-import App from '../../base'
-import importOverleafModules from '../../../macros/import-overleaf-module.macro'
-
-const eModules = importOverleafModules('editorToolbarButtons')
-const editorToolbarButtons = eModules.map(item => item.import.default)
-
-export default App.controller('EditorToolbarController', [
-  '$scope',
-  'ide',
-  function ($scope, ide) {
-    const editorButtons = []
-
-    for (const editorToolbarButton of editorToolbarButtons) {
-      const button = editorToolbarButton.button($scope, ide)
-
-      if (editorToolbarButton.source) {
-        editorButtons.push(button)
-      }
-    }
-
-    $scope.editorButtons = editorButtons
-  },
-])

+ 0 - 1
services/web/frontend/js/ide/toolbar/index.js

@@ -1,2 +1 @@
 import './EditorLoaderController'
 import './EditorLoaderController'
-import './EditorToolbarController'

+ 1 - 172
services/web/frontend/js/vendor/libs/sharejs.js

@@ -26,7 +26,7 @@ function _classCallCheck(instance, Constructor) { if (!(instance instanceof Cons
  * DS207: Consider shorter variations of null checks
  * DS207: Consider shorter variations of null checks
  * 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(['ace/ace','crypto-js/sha1', '@/utils/debugging'], function (_ignore, CryptoJSSHA1, { debugging, debugConsole }) {
+define(['crypto-js/sha1', '@/utils/debugging'], function (CryptoJSSHA1, { debugging, debugConsole }) {
   var append = void 0,
   var append = void 0,
       bootstrapTransform = void 0,
       bootstrapTransform = void 0,
       exports = void 0,
       exports = void 0,
@@ -1410,177 +1410,6 @@ define(['ace/ace','crypto-js/sha1', '@/utils/debugging'], function (_ignore, Cry
   MicroEvent.mixin(Doc);
   MicroEvent.mixin(Doc);
 
 
   exports.Doc = Doc;
   exports.Doc = Doc;
-  // This is some utility code to connect an ace editor to a sharejs document.
-
-  var _ace$require = ace.require('ace/range'),
-      Range = _ace$require.Range;
-
-  // Convert an ace delta into an op understood by share.js
-
-
-  var applyAceToShareJS = function applyAceToShareJS(editorDoc, delta, doc, fromUndo) {
-    // Get the start position of the range, in no. of characters
-    var getStartOffsetPosition = function getStartOffsetPosition(start) {
-      // This is quite inefficient - getLines makes a copy of the entire
-      // lines array in the document. It would be nice if we could just
-      // access them directly.
-      var lines = editorDoc.getLines(0, start.row);
-
-      var offset = 0;
-
-      for (var i = 0; i < lines.length; i++) {
-        var line = lines[i];
-        offset += i < start.row ? line.length : start.column;
-      }
-
-      // Add the row number to include newlines.
-      return offset + start.row;
-    };
-
-    var pos = getStartOffsetPosition(delta.start);
-
-    // NOTE: Keep in sync with EditorWatchdogManager.
-    switch (delta.action) {
-      case 'insert':
-        text = delta.lines.join('\n');
-        doc.insert(pos, text, fromUndo);
-        break;
-
-      case 'remove':
-        text = delta.lines.join('\n');
-        doc.del(pos, text.length, fromUndo);
-        break;
-
-      default:
-        throw new Error('unknown action: ' + delta.action);
-    }
-  };
-
-  // Attach an ace editor to the document. The editor's contents are replaced
-  // with the document's contents.
-  window.sharejs.extendDoc('attach_ace', function (editor, maxDocLength) {
-    if (!this.provides['text']) {
-      throw new Error('Only text documents can be attached to ace');
-    }
-
-    var doc = this;
-    var editorDoc = editor.getSession().getDocument();
-    editorDoc.setNewLineMode('unix');
-
-    function check() {
-      return window.setTimeout(function () {
-        var editorText = editorDoc.getValue();
-        var otText = doc.getText();
-
-        if (editorText !== otText) {
-          doc.emit('error','Text does not match in ace')
-          debugConsole.error('Text does not match!');
-          debugConsole.error('editor: ' + editorText);
-          debugConsole.error('ot:     ' + otText);
-        }
-      }
-      // Should probably also replace the editor text with the doc snapshot.
-      , 0);
-    };
-
-    onDelete(0, editorDoc.getValue());
-    onInsert(0, doc.getText());
-
-    check();
-
-    // Listen for edits in ace
-    function editorListener(change) {
-      if (change.origin === 'remote') {
-        // this change has been injected via sharejs
-        return;
-      }
-
-      if (maxDocLength != null && editorDoc.getValue().length >= maxDocLength) {
-        doc.emit('error', new Error('document length is greater than maxDocLength'));
-        return;
-      }
-
-      var fromUndo = !!(editor.getSession().$fromUndo || editor.getSession().$fromReject);
-
-      applyAceToShareJS(editorDoc, change, doc, fromUndo);
-
-      return check();
-    };
-
-    editorDoc.on('change', editorListener);
-
-    // Horribly inefficient.
-    function offsetToPos(offset) {
-      // Again, very inefficient.
-      var lines = editorDoc.getAllLines();
-
-      var row = 0;
-      for (row = 0; row < lines.length; row++) {
-        var line = lines[row];
-        if (offset <= line.length) {
-          break;
-        }
-
-        // +1 for the newline.
-        offset -= lines[row].length + 1;
-      }
-
-      return { row: row, column: offset };
-    };
-
-    // We want to insert the flag `origin: 'remote'` into the delta if the op
-    //  is the initial document write or comes from the underlying sharejs doc
-    //  (which means it is from a remote op), so we have to do the work of
-    //  editorDoc.insert and editorDoc.remove manually.
-    // These methods are copied from ace.js doc#insert and #remove, and then
-    //  inject the `origin: 'remote'` flag into the delta.
-    function onInsert(pos, text) {
-      if (editorDoc.getLength() <= 1) {
-        editorDoc.$detectNewLine(text);
-      }
-
-      var lines = editorDoc.$split(text);
-      var position = offsetToPos(pos);
-      var start = editorDoc.clippedPos(position.row, position.column);
-      var end = {
-        row: start.row + lines.length - 1,
-        column: (lines.length === 1 ? start.column : 0) + lines[lines.length - 1].length
-      };
-
-      editorDoc.applyDelta({
-        start: start,
-        end: end,
-        action: 'insert',
-        lines: lines,
-        origin: 'remote'
-      });
-      return check();
-    };
-
-    function onDelete(pos, text) {
-      var range = Range.fromPoints(offsetToPos(pos), offsetToPos(pos + text.length));
-      var start = editorDoc.clippedPos(range.start.row, range.start.column);
-      var end = editorDoc.clippedPos(range.end.row, range.end.column);
-      editorDoc.applyDelta({
-        start: start,
-        end: end,
-        action: 'remove',
-        lines: editorDoc.getLinesForRange({ start: start, end: end }),
-        origin: 'remote'
-      });
-      return check();
-    };
-
-    doc.on('insert', onInsert);
-    doc.on('delete', onDelete);
-
-    doc.detach_ace = function () {
-      doc.removeListener('insert', onInsert);
-      doc.removeListener('delete', onDelete);
-      editorDoc.removeListener('change', editorListener);
-      return delete doc.detach_ace;
-    };
-  });
 
 
   return window.sharejs;
   return window.sharejs;
 });
 });

+ 0 - 1
services/web/frontend/stories/decorators/scope.tsx

@@ -92,7 +92,6 @@ const initialize = () => {
     toggleHistory: () => {},
     toggleHistory: () => {},
     editor: {
     editor: {
       richText: false,
       richText: false,
-      newSourceEditor: false,
       sharejs_doc: {
       sharejs_doc: {
         doc_id: 'test-doc',
         doc_id: 'test-doc',
         getSnapshot: () => 'some doc content',
         getSnapshot: () => 'some doc content',

+ 1 - 1
services/web/frontend/stories/editor-switch.stories.jsx

@@ -1,4 +1,4 @@
-import EditorSwitch from '../js/features/source-editor/components/editor-switch-legacy'
+import EditorSwitch from '../js/features/source-editor/components/editor-switch'
 import { ScopeDecorator } from './decorators/scope'
 import { ScopeDecorator } from './decorators/scope'
 
 
 export default {
 export default {

+ 0 - 105
services/web/frontend/stylesheets/app/editor.less

@@ -93,13 +93,6 @@
   .full-size;
   .full-size;
 }
 }
 
 
-.editor-container #editor {
-  top: @editor-toolbar-height;
-}
-.editor-container.has-source-toolbar #editor {
-  top: 0;
-}
-
 .pdf-empty,
 .pdf-empty,
 .no-history-available,
 .no-history-available,
 .no-file-selection,
 .no-file-selection,
@@ -276,104 +269,6 @@
   }
   }
 }
 }
 
 
-/**************************************
-Ace
-***************************************/
-
-// The internal components of the aceEditor directive
-.ace-editor-wrapper {
-  .full-size;
-  .undo-conflict-warning {
-    position: absolute;
-    top: 0;
-    right: 0;
-    left: 0;
-    z-index: 10;
-  }
-  .ace-editor-body {
-    width: 100%;
-    height: 100%;
-  }
-  .spelling-highlight {
-    z-index: 3;
-    position: absolute;
-    background-image: url(../../../public/img/spellcheck-underline.png);
-    @media (-webkit-min-device-pixel-ratio: 2), (min-resolution: 192dpi) {
-      background-image: url(../../../public/img/spellcheck-underline@2x.png);
-      background-size: 5px 4px;
-    }
-    background-repeat: repeat-x;
-    background-position: bottom left;
-  }
-  .remote-cursor {
-    position: absolute;
-    border-left: 2px solid transparent;
-    // Adds "nubbin" top right of cursor, which inherits the injected color
-    &::before {
-      content: '';
-      position: absolute;
-      left: -2px;
-      top: -5px;
-      height: 5px;
-      width: 5px;
-      border-top-width: 3px;
-      border-right-width: 3px;
-      border-bottom-width: 2px;
-      border-left-width: 2px;
-      border-style: solid;
-      border-color: inherit;
-    }
-  }
-  .annotation-label {
-    padding: (@line-height-computed / 4) (@line-height-computed / 2);
-    font-size: 0.8rem;
-    z-index: 100;
-    font-family: @font-family-sans-serif;
-    color: white;
-    font-weight: 700;
-    white-space: nowrap;
-  }
-  .annotation {
-    position: absolute;
-    z-index: 2;
-  }
-  .highlights-before-label,
-  .highlights-after-label {
-    position: absolute;
-    right: @line-height-computed;
-    z-index: 1;
-  }
-  .highlights-before-label {
-    top: @line-height-computed / 2;
-  }
-  .highlights-after-label {
-    bottom: @line-height-computed / 2;
-  }
-}
-
-.strike-through-foreground::after {
-  content: '';
-  position: absolute;
-  width: 100%;
-  top: 50%;
-  margin-top: -1px;
-  height: 2px;
-  background: currentColor;
-}
-
-// Hack to solve an issue where scrollbars aren't visible in Safari.
-// Safari seems to clip part of the scrollbar element. By giving the
-// element a background, we're telling Safari that it *really* needs to
-// paint the whole area. See https://github.com/ajaxorg/ace/issues/2872
-.ace_scrollbar-inner {
-  background-color: #fff;
-  opacity: 0.01;
-
-  .ace_dark & {
-    background-color: #000;
-  }
-}
-
 /**************************************
 /**************************************
 CodeMirror
 CodeMirror
 ***************************************/
 ***************************************/

+ 1 - 13
services/web/frontend/stylesheets/app/editor/review-panel.less

@@ -82,7 +82,7 @@
   }
   }
 
 
   position: absolute;
   position: absolute;
-  top: 32px;
+  top: 0px;
   bottom: 0px;
   bottom: 0px;
   right: 0px;
   right: 0px;
   background-color: @rp-bg-blue;
   background-color: @rp-bg-blue;
@@ -90,10 +90,6 @@
   font-size: @rp-base-font-size;
   font-size: @rp-base-font-size;
   color: @rp-type-blue;
   color: @rp-type-blue;
   z-index: 6;
   z-index: 6;
-
-  .has-source-toolbar & {
-    top: 0;
-  }
 }
 }
 
 
 .loading-panel {
 .loading-panel {
@@ -1128,10 +1124,6 @@ button when (@is-overleaf-light = true) {
   .rp-unsupported & {
   .rp-unsupported & {
     display: none;
     display: none;
   }
   }
-
-  .has-source-toolbar & {
-    top: 32px;
-  }
 }
 }
 
 
 .rp-track-changes-indicator {
 .rp-track-changes-indicator {
@@ -1376,8 +1368,4 @@ button when (@is-overleaf-light = true) {
   .rp-track-changes-indicator {
   .rp-track-changes-indicator {
     border: 0;
     border: 0;
   }
   }
-
-  .has-source-toolbar & {
-    top: 0;
-  }
 }
 }

+ 0 - 1
services/web/package.json

@@ -239,7 +239,6 @@
     "@uppy/utils": "^4.0.7",
     "@uppy/utils": "^4.0.7",
     "@uppy/xhr-upload": "^1.6.8",
     "@uppy/xhr-upload": "^1.6.8",
     "abort-controller": "^3.0.0",
     "abort-controller": "^3.0.0",
-    "ace-builds": "overleaf/ace-builds#v1.4.12-69aace50e6796d42116f8f96e19d2468d8a88af9",
     "acorn": "^7.1.1",
     "acorn": "^7.1.1",
     "acorn-walk": "^7.1.1",
     "acorn-walk": "^7.1.1",
     "algoliasearch": "^3.35.1",
     "algoliasearch": "^3.35.1",

+ 0 - 2
services/web/test/frontend/components/editor-left-menu/scope.tsx

@@ -10,7 +10,6 @@ type Scope = {
       doc_id?: string
       doc_id?: string
       getSnapshot?: () => string
       getSnapshot?: () => string
     }
     }
-    newSourceEditor?: boolean
   }
   }
   hasLintingError?: boolean
   hasLintingError?: boolean
   ui?: {
   ui?: {
@@ -45,7 +44,6 @@ export const mockScope = (scope?: Scope) => ({
       doc_id: 'test-doc',
       doc_id: 'test-doc',
       getSnapshot: () => 'some doc content',
       getSnapshot: () => 'some doc content',
     },
     },
-    newSourceEditor: true,
   },
   },
   hasLintingError: false,
   hasLintingError: false,
   ui: {
   ui: {

+ 0 - 1
services/web/test/frontend/features/editor-left-menu/components/help-show-hotkeys.test.jsx

@@ -15,7 +15,6 @@ describe('<HelpShowHotkeys />', function () {
     expect(screen.queryByRole('dialog')).to.equal(null)
     expect(screen.queryByRole('dialog')).to.equal(null)
     fireEvent.click(screen.getByRole('button', { name: 'Show Hotkeys' }))
     fireEvent.click(screen.getByRole('button', { name: 'Show Hotkeys' }))
     const modal = screen.getAllByRole('dialog')[0]
     const modal = screen.getAllByRole('dialog')[0]
-    within(modal).getByText('Hotkeys (Legacy source editor)')
     within(modal).getByText('Common')
     within(modal).getByText('Common')
   })
   })
 })
 })

+ 1 - 13
services/web/test/frontend/features/hotkeys-modal/components/hotkeys-modal.test.jsx

@@ -11,25 +11,13 @@ const modalProps = {
 
 
 describe('<HotkeysModal />', function () {
 describe('<HotkeysModal />', function () {
   it('renders the translated modal title on cm6', async function () {
   it('renders the translated modal title on cm6', async function () {
-    const { baseElement } = render(
-      <HotkeysModal {...modalProps} newSourceEditor />
-    )
+    const { baseElement } = render(<HotkeysModal {...modalProps} />)
 
 
     expect(baseElement.querySelector('.modal-title').textContent).to.equal(
     expect(baseElement.querySelector('.modal-title').textContent).to.equal(
       'Hotkeys (Source editor)'
       'Hotkeys (Source editor)'
     )
     )
   })
   })
 
 
-  it('renders the translated modal title on ace', async function () {
-    const { baseElement } = render(
-      <HotkeysModal {...modalProps} newSourceEditor={false} />
-    )
-
-    expect(baseElement.querySelector('.modal-title').textContent).to.equal(
-      'Hotkeys (Legacy source editor)'
-    )
-  })
-
   it('renders translated heading with embedded code', function () {
   it('renders translated heading with embedded code', function () {
     const { baseElement } = render(<HotkeysModal {...modalProps} />)
     const { baseElement } = render(<HotkeysModal {...modalProps} />)
 
 

+ 0 - 204
services/web/test/frontend/features/outline/outline-parser.test.js

@@ -1,204 +0,0 @@
-import { expect } from 'chai'
-
-import {
-  matchOutline,
-  nestOutline,
-} from '../../../../frontend/js/features/outline/outline-parser'
-
-describe('OutlineParser', function () {
-  describe('matchOutline', function () {
-    it('matches all levels', function () {
-      const content = `
-      \\book{Book}
-      \\part{Part}
-      \\addpart{Part 2}
-      \\chapter{Chapter}
-      \\addchap{Chapter 2}
-      \\section{Section 1}
-      \\addsec{Section 1b}
-      \\subsection{Subsection}
-      \\subsubsection{Subsubsection}
-      \\section{Section 2}
-      \\subsubsection{Subsubsection without subsection}
-      \\paragraph{a paragraph} Here is some text.
-      \\subparagraph{a subparagraph} Here is some more text.
-    `
-      const outline = matchOutline(content)
-      expect(outline).to.deep.equal([
-        { line: 2, title: 'Book', level: 10 },
-        { line: 3, title: 'Part', level: 20 },
-        { line: 4, title: 'Part 2', level: 20 },
-        { line: 5, title: 'Chapter', level: 30 },
-        { line: 6, title: 'Chapter 2', level: 30 },
-        { line: 7, title: 'Section 1', level: 40 },
-        { line: 8, title: 'Section 1b', level: 40 },
-        { line: 9, title: 'Subsection', level: 50 },
-        { line: 10, title: 'Subsubsection', level: 60 },
-        { line: 11, title: 'Section 2', level: 40 },
-        { line: 12, title: 'Subsubsection without subsection', level: 60 },
-        { line: 13, title: 'a paragraph', level: 70 },
-        { line: 14, title: 'a subparagraph', level: 80 },
-      ])
-    })
-
-    it('matches display titles', function () {
-      const content = `
-      \\section{\\label{foo} Label before}
-      \\section{Label after \\label{foo}}
-      \\section{Label \\label{foo} between}
-      \\section{TT \\texttt{Bar}}
-      \\section{plain title}
-      `
-      const outline = matchOutline(content)
-      expect(outline).to.deep.equal([
-        { line: 2, title: ' Label before', level: 40 },
-        { line: 3, title: 'Label after ', level: 40 },
-        { line: 4, title: 'Label  between', level: 40 },
-        { line: 5, title: 'TT Bar', level: 40 },
-        { line: 6, title: 'plain title', level: 40 },
-      ])
-    })
-
-    it('removes spurious commands after title definition', function () {
-      const content = `
-      \\section{Plain title} more text \\href{link}{link}
-      \\section{\\label{foo} Label before} more text \\href{link}{link}
-      `
-      const outline = matchOutline(content)
-      expect(outline).to.deep.equal([
-        { line: 2, title: 'Plain title', level: 40 },
-        { line: 3, title: ' Label before', level: 40 },
-      ])
-    })
-
-    it('matches empty sections', function () {
-      const outline = matchOutline('\\section{}')
-      expect(outline).to.deep.equal([{ line: 1, title: '', level: 40 }])
-    })
-
-    it('matches indented sections', function () {
-      const outline = matchOutline('\t\\section{Indented}')
-      expect(outline).to.deep.equal([{ line: 1, title: 'Indented', level: 40 }])
-    })
-
-    it('matches unnumbered sections', function () {
-      const outline = matchOutline('\\section*{Unnumbered}')
-      expect(outline).to.deep.equal([
-        { line: 1, title: 'Unnumbered', level: 40 },
-      ])
-    })
-
-    it('matches short titles', function () {
-      const outline = matchOutline(
-        '\\chapter[Short Title For TOC]{Very Long Title for Text}'
-      )
-      expect(outline).to.deep.equal([
-        { line: 1, title: 'Short Title For TOC', level: 30 },
-      ])
-    })
-
-    it('handles spacing', function () {
-      const content = `
-      \\section {Weird Spacing}
-      \\section * {Weird Spacing Unnumbered}
-      \\section [Weird Spacing for TOC] {Weird Spacing}
-    `
-      const outline = matchOutline(content)
-      expect(outline).to.deep.equal([
-        { line: 2, title: 'Weird Spacing', level: 40 },
-        { line: 3, title: 'Weird Spacing Unnumbered', level: 40 },
-        { line: 4, title: 'Weird Spacing for TOC', level: 40 },
-      ])
-    })
-
-    it("doesn't match commented lines", function () {
-      const content = `
-      % \\section{I should not appear in the outline}
-    `
-      const outline = matchOutline(content)
-      expect(outline).to.deep.equal([])
-    })
-
-    it("doesn't match inline sections", function () {
-      const content = `
-      I like to write \\section{inline} on one line.
-    `
-      const outline = matchOutline(content)
-      expect(outline).to.deep.equal([])
-    })
-  })
-
-  describe('nestOutline', function () {
-    it('matches all levels', function () {
-      const flatOutline = [
-        { line: 10, title: 'Book', level: 10 },
-        { line: 20, title: 'Part A', level: 20 },
-        { line: 30, title: 'Section A 1', level: 40 },
-        { line: 40, title: 'Subsection A 1 1', level: 50 },
-        { line: 50, title: 'Subsection A 1 2', level: 50 },
-        { line: 60, title: 'Section A 2', level: 40 },
-        { line: 70, title: 'Section A 3', level: 40 },
-        { line: 80, title: 'Subsection A 3 1', level: 50 },
-        { line: 90, title: 'Chapter', level: 30 },
-        { line: 100, title: 'Part B', level: 20 },
-        { line: 110, title: 'Section 2', level: 40 },
-        { line: 120, title: 'Subsubsection without subsection', level: 60 },
-      ]
-      const nestedOutline = nestOutline(flatOutline)
-      expect(nestedOutline).to.deep.equal([
-        {
-          line: 10,
-          title: 'Book',
-          level: 10,
-          children: [
-            {
-              line: 20,
-              title: 'Part A',
-              level: 20,
-              children: [
-                {
-                  line: 30,
-                  title: 'Section A 1',
-                  level: 40,
-                  children: [
-                    { line: 40, title: 'Subsection A 1 1', level: 50 },
-                    { line: 50, title: 'Subsection A 1 2', level: 50 },
-                  ],
-                },
-                { line: 60, title: 'Section A 2', level: 40 },
-                {
-                  line: 70,
-                  title: 'Section A 3',
-                  level: 40,
-                  children: [
-                    { line: 80, title: 'Subsection A 3 1', level: 50 },
-                  ],
-                },
-                { line: 90, title: 'Chapter', level: 30 },
-              ],
-            },
-            {
-              line: 100,
-              title: 'Part B',
-              level: 20,
-              children: [
-                {
-                  line: 110,
-                  title: 'Section 2',
-                  level: 40,
-                  children: [
-                    {
-                      line: 120,
-                      title: 'Subsubsection without subsection',
-                      level: 60,
-                    },
-                  ],
-                },
-              ],
-            },
-          ],
-        },
-      ])
-    })
-  })
-})

+ 0 - 17
services/web/webpack.config.js

@@ -61,18 +61,11 @@ function getModuleDirectory(moduleName) {
 
 
 const mathjaxDir = getModuleDirectory('mathjax')
 const mathjaxDir = getModuleDirectory('mathjax')
 const mathjax3Dir = getModuleDirectory('mathjax-3')
 const mathjax3Dir = getModuleDirectory('mathjax-3')
-const aceDir = getModuleDirectory('ace-builds')
 
 
 const pdfjsVersions = ['pdfjs-dist213', 'pdfjs-dist401']
 const pdfjsVersions = ['pdfjs-dist213', 'pdfjs-dist401']
 
 
 const vendorDir = path.join(__dirname, 'frontend/js/vendor')
 const vendorDir = path.join(__dirname, 'frontend/js/vendor')
 
 
-const ACE_VERSION = require('ace-builds/version')
-if (ACE_VERSION !== PackageVersions.version.ace) {
-  throw new Error(
-    '"ace-builds" version de-synced, update services/web/app/src/infrastructure/PackageVersions.js'
-  )
-}
 const MATHJAX_VERSION = require('mathjax/package.json').version
 const MATHJAX_VERSION = require('mathjax/package.json').version
 if (MATHJAX_VERSION !== PackageVersions.version.mathjax) {
 if (MATHJAX_VERSION !== PackageVersions.version.mathjax) {
   throw new Error(
   throw new Error(
@@ -236,11 +229,6 @@ module.exports = {
   },
   },
   resolve: {
   resolve: {
     alias: {
     alias: {
-      // Aliases for AMD modules
-
-      // Enables ace/ace shortcut
-      ace: 'ace-builds/src-noconflict',
-
       // custom prefixes for import paths
       // custom prefixes for import paths
       '@': path.resolve(__dirname, './frontend/js/'),
       '@': path.resolve(__dirname, './frontend/js/'),
     },
     },
@@ -335,11 +323,6 @@ module.exports = {
           to: 'js/libs/mathjax',
           to: 'js/libs/mathjax',
           context: mathjaxDir,
           context: mathjaxDir,
         },
         },
-        {
-          from: 'src-min-noconflict',
-          to: `js/ace-${PackageVersions.version.ace}/`,
-          context: aceDir,
-        },
         ...pdfjsVersions.flatMap(version => {
         ...pdfjsVersions.flatMap(version => {
           const dir = getModuleDirectory(version)
           const dir = getModuleDirectory(version)
 
 

Некоторые файлы не были показаны из-за большого количества измененных файлов