Explorar o código

Merge pull request #266 from sharelatex/as-cm

CodeMirror
Alasdair Smith %!s(int64=8) %!d(string=hai) anos
pai
achega
7ff0f40477

+ 4 - 0
services/web/Gruntfile.coffee

@@ -1,5 +1,6 @@
 fs = require "fs"
 PackageVersions = require "./app/coffee/infrastructure/PackageVersions"
+Settings = require "settings-sharelatex"
 require('es6-promise').polyfill()
 
 module.exports = (grunt) ->
@@ -108,6 +109,7 @@ module.exports = (grunt) ->
 						"public/coffee/ide/editor/sharejs/vendor/client/microevent.coffee"
 						"public/coffee/ide/editor/sharejs/vendor/client/doc.coffee"
 						"public/coffee/ide/editor/sharejs/vendor/client/ace.coffee"
+						"public/coffee/ide/editor/sharejs/vendor/client/cm.coffee"
 					]
 
 			client:
@@ -152,6 +154,7 @@ module.exports = (grunt) ->
 					sourceMapBasepath: "public/stylesheets"
 					globalVars:
 						'is-overleaf': false
+						'show-rich-text': Settings.showRichText
 				files:
 					"public/stylesheets/style.css": "public/stylesheets/style.less"
 			ol:
@@ -161,6 +164,7 @@ module.exports = (grunt) ->
 					sourceMapBasepath: "public/stylesheets"
 					globalVars:
 						'is-overleaf': true
+						'show-rich-text': Settings.showRichText
 				files:
 					"public/stylesheets/ol-style.css": "public/stylesheets/ol-style.less"
 

+ 2 - 1
services/web/Makefile

@@ -21,7 +21,8 @@ SHAREJS_COFFEE_FILES := \
 	public/coffee/ide/editor/sharejs/vendor/types/text-api.coffee \
 	public/coffee/ide/editor/sharejs/vendor/client/microevent.coffee \
 	public/coffee/ide/editor/sharejs/vendor/client/doc.coffee \
-	public/coffee/ide/editor/sharejs/vendor/client/ace.coffee
+	public/coffee/ide/editor/sharejs/vendor/client/ace.coffee \
+	public/coffee/ide/editor/sharejs/vendor/client/cm.coffee
 LESS_FILES := $(shell find public/stylesheets -name '*.less')
 CSS_FILES := public/stylesheets/style.css public/stylesheets/ol-style.css
 

+ 2 - 0
services/web/app/coffee/infrastructure/Features.coffee

@@ -14,5 +14,7 @@ module.exports = Features =
 				return Settings.enableGithubSync
 			when 'v1-return-message'
 				return Settings.accountMerge? and Settings.overleaf?
+			when 'rich-text'
+				return Settings.showRichText
 			else
 				throw new Error("unknown feature: #{feature}")

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

@@ -158,6 +158,13 @@ block requirejs
 		window.pdfCMapsPath = "#{pdfCMapsPath}"
 		window.uiConfig = JSON.parse('!{JSON.stringify(uiConfig).replace(/\//g, "\\/")}');
 
+	// TODO: inject based on environment
+	if hasFeature('rich-text')
+		script(
+			src="http://localhost:3809/public/js/es/richText.js"
+			type="text/javascript"
+		)
+
 	script(
 		data-main=buildJsPath("ide.js", {hashedPath:false}),
 		baseurl=fullJsPath,

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

@@ -30,9 +30,13 @@ div.full-size(
 				i.fa.fa-arrow-left
 				|   #{translate("open_a_file_on_the_left")}
 
+		if hasFeature('rich-text')
+			.toolbar.toolbar-editor(ng-controller="EditorToolbarController")
+				button(ng-click="toggleRichText()") Rich Text
+
 		#editor(
 			ace-editor="editor",
-			ng-show="!!editor.sharejs_doc && !editor.opening"
+			ng-if="!!editor.sharejs_doc && !editor.opening && !editor.richText"
 			theme="settings.theme",
 			keybindings="settings.mode",
 			font-size="settings.fontSize",
@@ -63,6 +67,13 @@ div.full-size(
 			renderer-data="reviewPanel.rendererData"
 		)
 
+		if hasFeature('rich-text')
+			#editor-rich-text(
+				cm-editor
+				ng-if="!editor.opening && editor.richText"
+				sharejs-doc="editor.sharejs_doc"
+			)
+
 		include ./review-panel
 
 	.ui-layout-east

+ 5 - 0
services/web/npm-shrinkwrap.json

@@ -1354,6 +1354,11 @@
       "from": "code-point-at@>=1.0.0 <2.0.0",
       "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz"
     },
+    "codemirror": {
+      "version": "5.33.0",
+      "from": "codemirror@latest",
+      "resolved": "https://registry.npmjs.org/codemirror/-/codemirror-5.33.0.tgz"
+    },
     "coffee-script": {
       "version": "1.12.4",
       "from": "coffee-script@1.12.4",

+ 1 - 0
services/web/package.json

@@ -30,6 +30,7 @@
     "bcrypt": "1.0.1",
     "body-parser": "^1.13.1",
     "bufferedstream": "1.6.0",
+    "codemirror": "^5.33.0",
     "connect-redis": "^3.1.0",
     "cookie": "^0.2.3",
     "cookie-parser": "1.3.5",

+ 31 - 19
services/web/public/coffee/ide/editor/Document.coffee

@@ -26,7 +26,8 @@ define [
 			@connected = @ide.socket.socket.connected
 			@joined = false
 			@wantToBeJoined = false
-			@_checkConsistency = _.bind(@_checkConsistency, @)
+			@_checkAceConsistency = _.bind(@_checkConsistency, @, @ace)
+			@_checkCMConsistency = _.bind(@_checkConsistency, @, @cm)
 			@inconsistentCount = 0
 			@_bindToEditorEvents()
 			@_bindToSocketEvents()
@@ -34,32 +35,43 @@ define [
 		attachToAce: (@ace) ->
 			@doc?.attachToAce(@ace)
 			editorDoc = @ace.getSession().getDocument()
-			editorDoc.on "change", @_checkConsistency
+			editorDoc.on "change", @_checkAceConsistency
 			@ide.$scope.$emit 'document:opened', @doc
 
 		detachFromAce: () ->
 			@doc?.detachFromAce()
 			editorDoc = @ace?.getSession().getDocument()
-			editorDoc?.off "change", @_checkConsistency
+			editorDoc?.off "change", @_checkAceConsistency
 			@ide.$scope.$emit 'document:closed', @doc
-		
+
+		attachToCM: (@cm) ->
+			@doc?.attachToCM(@cm)
+			@cm?.on "change", @_checkCMConsistency
+			@ide.$scope.$emit 'document:opened', @doc
+
+		detachFromCM: () ->
+			@doc?.detachFromCM()
+			@cm?.off "change", @_checkCMConsistency
+			@ide.$scope.$emit 'document:closed', @doc
+
 		submitOp: (args...) -> @doc?.submitOp(args...)
 
-		_checkConsistency: () ->
-			# We've been seeing a lot of errors when I think there shouldn't be
-			# any, which may be related to this check happening before the change is
-			# applied. If we use a timeout, hopefully we can reduce this.
-			setTimeout () =>
-				editorValue = @ace?.getValue()
-				sharejsValue = @doc?.getSnapshot()
-				if editorValue != sharejsValue
-					@inconsistentCount++
-				else
-					@inconsistentCount = 0
-
-				if @inconsistentCount >= 3
-					@_onError new Error("Editor text does not match server text")
-			, 0
+		_checkConsistency: (editor) ->
+			return () =>
+				# We've been seeing a lot of errors when I think there shouldn't be
+				# any, which may be related to this check happening before the change is
+				# applied. If we use a timeout, hopefully we can reduce this.
+				setTimeout () =>
+					editorValue = editor?.getValue()
+					sharejsValue = @doc?.getSnapshot()
+					if editorValue != sharejsValue
+						@inconsistentCount++
+					else
+						@inconsistentCount = 0
+
+					if @inconsistentCount >= 3
+						@_onError new Error("Editor text does not match server text")
+				, 0
 
 		getSnapshot: () ->
 			@doc?.getSnapshot()

+ 6 - 0
services/web/public/coffee/ide/editor/EditorManager.coffee

@@ -1,7 +1,9 @@
 define [
 	"ide/editor/Document"
 	"ide/editor/directives/aceEditor"
+	"ide/editor/directives/cmEditor"
 	"ide/editor/controllers/SavingNotificationController"
+	"ide/editor/controllers/EditorToolbarController"
 ], (Document) ->
 	class EditorManager
 		constructor: (@ide, @$scope) ->
@@ -12,6 +14,7 @@ define [
 				opening: true
 				trackChanges: false
 				wantTrackChanges: false
+				richText: false
 			}
 
 			@$scope.$on "entity:selected", (event, entity) =>
@@ -186,3 +189,6 @@ define [
 						@$scope.editor.trackChanges = want
 				else
 					@_syncTimeout = setTimeout tryToggle, 100
+
+		toggleRichText: () ->
+			@$scope.editor.richText = !@$scope.editor.richText

+ 3 - 0
services/web/public/coffee/ide/editor/ShareJsDoc.coffee

@@ -120,6 +120,9 @@ define [
 		attachToAce: (ace) -> @_doc.attach_ace(ace, false, window.maxDocLength)
 		detachFromAce: () -> @_doc.detach_ace?()
 
+		attachToCM: (cm) -> @_doc.attach_cm(cm, false)
+		detachFromCM: () -> @_doc.detach_cm?()
+
 		INFLIGHT_OP_TIMEOUT: 5000 # Retry sending ops after 5 seconds without an ack
 		WAIT_FOR_CONNECTION_TIMEOUT: 500 # If we're waiting for the project to join, try again in 0.5 seconds
 		_startInflightOpTimeout: (update) ->

+ 7 - 0
services/web/public/coffee/ide/editor/controllers/EditorToolbarController.coffee

@@ -0,0 +1,7 @@
+define [
+	"base"
+	"ide/editor/Document"
+], (App, Document) ->
+  App.controller "EditorToolbarController", ($scope, ide) ->
+    $scope.toggleRichText = () ->
+      ide.editorManager.toggleRichText()

+ 4 - 2
services/web/public/coffee/ide/editor/directives/aceEditor.coffee

@@ -97,7 +97,7 @@ define [
 				scope.name = attrs.aceEditor
 
 				if scope.spellCheck # only enable spellcheck when explicitly required
-					spellCheckCache =  $cacheFactory("spellCheck-#{scope.name}", {capacity: 1000})
+					spellCheckCache = $cacheFactory.get("spellCheck-#{scope.name}") || $cacheFactory("spellCheck-#{scope.name}", {capacity: 1000})
 					spellCheckManager = new SpellCheckManager(scope, editor, element, spellCheckCache, $http, $q)
 				undoManager           = new UndoManager(scope, editor, element)
 				highlightsManager     = new HighlightsManager(scope, editor, element)
@@ -106,7 +106,6 @@ define [
 				metadataManager = new MetadataManager(scope, editor, element, metadata)
 				autoCompleteManager = new AutoCompleteManager(scope, editor, element, metadataManager, graphics, preamble, files)
 
-
 				# Prevert Ctrl|Cmd-S from triggering save dialog
 				editor.commands.addCommand
 					name: "save",
@@ -428,6 +427,9 @@ define [
 					if rendererData?
 						rendererData.lineHeight = editor.renderer.lineHeight
 
+				scope.$on '$destroy', () ->
+					detachFromAce(scope.sharejsDoc)
+
 			template: """
 				<div class="ace-editor-wrapper">
 					<div

+ 32 - 0
services/web/public/coffee/ide/editor/directives/cmEditor.coffee

@@ -0,0 +1,32 @@
+define [
+  "base"
+], (App) ->
+  App.directive "cmEditor", () ->
+    return {
+      scope: {
+        sharejsDoc: "="
+      }
+
+      link: (scope, element, attrs) ->
+        cm = Frontend.richText.init(element.find('.cm-editor-wrapper')[0])
+
+        scope.$watch "sharejsDoc", (sharejsDoc, oldSharejsDoc) ->
+          if oldSharejsDoc?
+            detachFromCM(oldSharejsDoc)
+          if sharejsDoc?
+            attachToCM(sharejsDoc)
+
+        attachToCM = (sharejsDoc) ->
+          cm.setValue(sharejsDoc.getSnapshot())
+          sharejsDoc.attachToCM(cm)
+
+        detachFromCM = (sharejsDoc) ->
+          sharejsDoc.detachFromCM()
+
+        scope.$on 'destroy', () ->
+          detachFromCM(scope.sharejsDoc)
+
+      template: """
+        <div class="cm-editor-wrapper"></div>
+      """
+    }

+ 2 - 2
services/web/public/coffee/ide/editor/sharejs/vendor/client/ace.coffee

@@ -3,7 +3,7 @@
 Range = ace.require("ace/range").Range
 
 # Convert an ace delta into an op understood by share.js
-applyToShareJS = (editorDoc, delta, doc, fromUndo) ->
+applyAceToShareJS = (editorDoc, delta, doc, fromUndo) ->
   # Get the start position of the range, in no. of characters
   getStartOffsetPosition = (start) ->
     # This is quite inefficient - getLines makes a copy of the entire
@@ -81,7 +81,7 @@ window.sharejs.extendDoc 'attach_ace', (editor, keepEditorContents, maxDocLength
 
     fromUndo = !!(editor.getSession().$fromUndo or editor.getSession().$fromReject)
     
-    applyToShareJS editorDoc, change, doc, fromUndo
+    applyAceToShareJS editorDoc, change, doc, fromUndo
 
     check()
 

+ 7 - 7
services/web/public/coffee/ide/editor/sharejs/vendor/client/cm.coffee

@@ -3,7 +3,7 @@
 # It is heavily inspired from the Ace editor hook.
 
 # Convert a CodeMirror delta into an op understood by share.js
-applyToShareJS = (editorDoc, delta, doc) ->
+applyCMToShareJS = (editorDoc, delta, doc) ->
   # CodeMirror deltas give a text replacement.
   # I tuned this operation a little bit, for speed.
   startPos = 0  # Get character position from # of chars in each line.
@@ -26,7 +26,7 @@ applyToShareJS = (editorDoc, delta, doc) ->
     doc.del startPos, delLen
     doc.insert startPos, delta.text.join '\n' if delta.text
 
-  applyToShareJS editorDoc, delta.next, doc if delta.next
+  applyCMToShareJS editorDoc, delta.next, doc if delta.next
 
 # Attach a CodeMirror editor to the document. The editor's contents are replaced
 # with the document's contents unless keepEditorContents is true. (In which case
@@ -39,14 +39,14 @@ window.sharejs.extendDoc 'attach_cm', (editor, keepEditorContents) ->
   check = ->
     window.setTimeout ->
         editorText = editor.getValue()
-        otText = sharedoc.getValue()
+        otText = sharedoc.getText()
 
         if editorText != otText
           console.error "Text does not match!"
           console.error "editor: #{editorText}"
           console.error "ot:     #{otText}"
           # Replace the editor text with the doc snapshot.
-          editor.setValue sharedoc.getValue()
+          editor.setValue sharedoc.getText()
       , 0
 
   if keepEditorContents
@@ -64,11 +64,11 @@ window.sharejs.extendDoc 'attach_cm', (editor, keepEditorContents) ->
   # Listen for edits in CodeMirror.
   editorListener = (ed, change) ->
     return if suppress
-    applyToShareJS editor, change, sharedoc
+    applyCMToShareJS editor, change, sharedoc
 
     check()
 
-  editor.setOption 'onChange',  editorListener
+  editor.on 'change', editorListener
 
   @on 'insert', (pos, text) ->
     suppress = true
@@ -87,7 +87,7 @@ window.sharejs.extendDoc 'attach_cm', (editor, keepEditorContents) ->
 
   @detach_cm = ->
     # TODO: can we remove the insert and delete event callbacks?
-    editor.setOption 'onChange', null
+    editor.off 'onChange', editorListener
     delete @detach_cm
 
   return

+ 5 - 1
services/web/public/es/rich-text.js

@@ -1 +1,5 @@
-console.log('hello from rich text')
+import CodeMirror from 'codemirror'
+
+export function init(rootEl) {
+  return CodeMirror(rootEl)
+}

+ 6 - 0
services/web/public/stylesheets/_style_includes.less

@@ -80,6 +80,12 @@
 @import "app/error-pages.less";
 @import "app/v1-badge.less";
 
+// Vendor CSS
 @import	"../js/libs/pdfListView/TextLayer.css";
 @import	"../js/libs/pdfListView/AnnotationsLayer.css";
 @import	"../js/libs/pdfListView/HighlightsLayer.css";
+
+// CodeMirror
+& when (@show-rich-text) {
+  @import "vendor/codemirror.css";
+}

+ 16 - 1
services/web/public/stylesheets/app/editor.less

@@ -75,10 +75,17 @@
 	top: 40px;
 }
 
-#editor {
+#editor, #editor-rich-text {
 	.full-size;
 }
 
+#editor when (@show-rich-text = true) {
+	top: 40px; // TODO: replace with toolbar height var
+}
+#editor-rich-text when (@show-rich-text = true) {
+	top: 40px; // TODO: replace with toolbar height var
+}
+
 .loading-screen {
 	display: flex;
 	flex-direction: column;
@@ -190,6 +197,14 @@
 	}
 }
 
+.cm-editor-wrapper {
+	height: 100%;
+
+	.CodeMirror {
+		height: 100%;
+	}
+}
+
 // The internal components of the aceEditor directive
 .ace-editor-wrapper {
 	.full-size;

+ 346 - 0
services/web/public/stylesheets/vendor/codemirror.css

@@ -0,0 +1,346 @@
+/* BASICS */
+
+.CodeMirror {
+  /* Set height, width, borders, and global font properties here */
+  font-family: monospace;
+  height: 300px;
+  color: black;
+  direction: ltr;
+}
+
+/* PADDING */
+
+.CodeMirror-lines {
+  padding: 4px 0; /* Vertical padding around content */
+}
+.CodeMirror pre {
+  padding: 0 4px; /* Horizontal padding of content */
+}
+
+.CodeMirror-scrollbar-filler, .CodeMirror-gutter-filler {
+  background-color: white; /* The little square between H and V scrollbars */
+}
+
+/* GUTTER */
+
+.CodeMirror-gutters {
+  border-right: 1px solid #ddd;
+  background-color: #f7f7f7;
+  white-space: nowrap;
+}
+.CodeMirror-linenumbers {}
+.CodeMirror-linenumber {
+  padding: 0 3px 0 5px;
+  min-width: 20px;
+  text-align: right;
+  color: #999;
+  white-space: nowrap;
+}
+
+.CodeMirror-guttermarker { color: black; }
+.CodeMirror-guttermarker-subtle { color: #999; }
+
+/* CURSOR */
+
+.CodeMirror-cursor {
+  border-left: 1px solid black;
+  border-right: none;
+  width: 0;
+}
+/* Shown when moving in bi-directional text */
+.CodeMirror div.CodeMirror-secondarycursor {
+  border-left: 1px solid silver;
+}
+.cm-fat-cursor .CodeMirror-cursor {
+  width: auto;
+  border: 0 !important;
+  background: #7e7;
+}
+.cm-fat-cursor div.CodeMirror-cursors {
+  z-index: 1;
+}
+.cm-fat-cursor-mark {
+  background-color: rgba(20, 255, 20, 0.5);
+  -webkit-animation: blink 1.06s steps(1) infinite;
+  -moz-animation: blink 1.06s steps(1) infinite;
+  animation: blink 1.06s steps(1) infinite;
+}
+.cm-animate-fat-cursor {
+  width: auto;
+  border: 0;
+  -webkit-animation: blink 1.06s steps(1) infinite;
+  -moz-animation: blink 1.06s steps(1) infinite;
+  animation: blink 1.06s steps(1) infinite;
+  background-color: #7e7;
+}
+@-moz-keyframes blink {
+  0% {}
+  50% { background-color: transparent; }
+  100% {}
+}
+@-webkit-keyframes blink {
+  0% {}
+  50% { background-color: transparent; }
+  100% {}
+}
+@keyframes blink {
+  0% {}
+  50% { background-color: transparent; }
+  100% {}
+}
+
+/* Can style cursor different in overwrite (non-insert) mode */
+.CodeMirror-overwrite .CodeMirror-cursor {}
+
+.cm-tab { display: inline-block; text-decoration: inherit; }
+
+.CodeMirror-rulers {
+  position: absolute;
+  left: 0; right: 0; top: -50px; bottom: -20px;
+  overflow: hidden;
+}
+.CodeMirror-ruler {
+  border-left: 1px solid #ccc;
+  top: 0; bottom: 0;
+  position: absolute;
+}
+
+/* DEFAULT THEME */
+
+.cm-s-default .cm-header {color: blue;}
+.cm-s-default .cm-quote {color: #090;}
+.cm-negative {color: #d44;}
+.cm-positive {color: #292;}
+.cm-header, .cm-strong {font-weight: bold;}
+.cm-em {font-style: italic;}
+.cm-link {text-decoration: underline;}
+.cm-strikethrough {text-decoration: line-through;}
+
+.cm-s-default .cm-keyword {color: #708;}
+.cm-s-default .cm-atom {color: #219;}
+.cm-s-default .cm-number {color: #164;}
+.cm-s-default .cm-def {color: #00f;}
+.cm-s-default .cm-variable,
+.cm-s-default .cm-punctuation,
+.cm-s-default .cm-property,
+.cm-s-default .cm-operator {}
+.cm-s-default .cm-variable-2 {color: #05a;}
+.cm-s-default .cm-variable-3, .cm-s-default .cm-type {color: #085;}
+.cm-s-default .cm-comment {color: #a50;}
+.cm-s-default .cm-string {color: #a11;}
+.cm-s-default .cm-string-2 {color: #f50;}
+.cm-s-default .cm-meta {color: #555;}
+.cm-s-default .cm-qualifier {color: #555;}
+.cm-s-default .cm-builtin {color: #30a;}
+.cm-s-default .cm-bracket {color: #997;}
+.cm-s-default .cm-tag {color: #170;}
+.cm-s-default .cm-attribute {color: #00c;}
+.cm-s-default .cm-hr {color: #999;}
+.cm-s-default .cm-link {color: #00c;}
+
+.cm-s-default .cm-error {color: #f00;}
+.cm-invalidchar {color: #f00;}
+
+.CodeMirror-composing { border-bottom: 2px solid; }
+
+/* Default styles for common addons */
+
+div.CodeMirror span.CodeMirror-matchingbracket {color: #0b0;}
+div.CodeMirror span.CodeMirror-nonmatchingbracket {color: #a22;}
+.CodeMirror-matchingtag { background: rgba(255, 150, 0, .3); }
+.CodeMirror-activeline-background {background: #e8f2ff;}
+
+/* STOP */
+
+/* The rest of this file contains styles related to the mechanics of
+   the editor. You probably shouldn't touch them. */
+
+.CodeMirror {
+  position: relative;
+  overflow: hidden;
+  background: white;
+}
+
+.CodeMirror-scroll {
+  overflow: scroll !important; /* Things will break if this is overridden */
+  /* 30px is the magic margin used to hide the element's real scrollbars */
+  /* See overflow: hidden in .CodeMirror */
+  margin-bottom: -30px; margin-right: -30px;
+  padding-bottom: 30px;
+  height: 100%;
+  outline: none; /* Prevent dragging from highlighting the element */
+  position: relative;
+}
+.CodeMirror-sizer {
+  position: relative;
+  border-right: 30px solid transparent;
+}
+
+/* The fake, visible scrollbars. Used to force redraw during scrolling
+   before actual scrolling happens, thus preventing shaking and
+   flickering artifacts. */
+.CodeMirror-vscrollbar, .CodeMirror-hscrollbar, .CodeMirror-scrollbar-filler, .CodeMirror-gutter-filler {
+  position: absolute;
+  z-index: 6;
+  display: none;
+}
+.CodeMirror-vscrollbar {
+  right: 0; top: 0;
+  overflow-x: hidden;
+  overflow-y: scroll;
+}
+.CodeMirror-hscrollbar {
+  bottom: 0; left: 0;
+  overflow-y: hidden;
+  overflow-x: scroll;
+}
+.CodeMirror-scrollbar-filler {
+  right: 0; bottom: 0;
+}
+.CodeMirror-gutter-filler {
+  left: 0; bottom: 0;
+}
+
+.CodeMirror-gutters {
+  position: absolute; left: 0; top: 0;
+  min-height: 100%;
+  z-index: 3;
+}
+.CodeMirror-gutter {
+  white-space: normal;
+  height: 100%;
+  display: inline-block;
+  vertical-align: top;
+  margin-bottom: -30px;
+}
+.CodeMirror-gutter-wrapper {
+  position: absolute;
+  z-index: 4;
+  background: none !important;
+  border: none !important;
+}
+.CodeMirror-gutter-background {
+  position: absolute;
+  top: 0; bottom: 0;
+  z-index: 4;
+}
+.CodeMirror-gutter-elt {
+  position: absolute;
+  cursor: default;
+  z-index: 4;
+}
+.CodeMirror-gutter-wrapper ::selection { background-color: transparent }
+.CodeMirror-gutter-wrapper ::-moz-selection { background-color: transparent }
+
+.CodeMirror-lines {
+  cursor: text;
+  min-height: 1px; /* prevents collapsing before first draw */
+}
+.CodeMirror pre {
+  /* Reset some styles that the rest of the page might have set */
+  -moz-border-radius: 0; -webkit-border-radius: 0; border-radius: 0;
+  border-width: 0;
+  background: transparent;
+  font-family: inherit;
+  font-size: inherit;
+  margin: 0;
+  white-space: pre;
+  word-wrap: normal;
+  line-height: inherit;
+  color: inherit;
+  z-index: 2;
+  position: relative;
+  overflow: visible;
+  -webkit-tap-highlight-color: transparent;
+  -webkit-font-variant-ligatures: contextual;
+  font-variant-ligatures: contextual;
+}
+.CodeMirror-wrap pre {
+  word-wrap: break-word;
+  white-space: pre-wrap;
+  word-break: normal;
+}
+
+.CodeMirror-linebackground {
+  position: absolute;
+  left: 0; right: 0; top: 0; bottom: 0;
+  z-index: 0;
+}
+
+.CodeMirror-linewidget {
+  position: relative;
+  z-index: 2;
+  padding: 0.1px; /* Force widget margins to stay inside of the container */
+}
+
+.CodeMirror-widget {}
+
+.CodeMirror-rtl pre { direction: rtl; }
+
+.CodeMirror-code {
+  outline: none;
+}
+
+/* Force content-box sizing for the elements where we expect it */
+.CodeMirror-scroll,
+.CodeMirror-sizer,
+.CodeMirror-gutter,
+.CodeMirror-gutters,
+.CodeMirror-linenumber {
+  -moz-box-sizing: content-box;
+  box-sizing: content-box;
+}
+
+.CodeMirror-measure {
+  position: absolute;
+  width: 100%;
+  height: 0;
+  overflow: hidden;
+  visibility: hidden;
+}
+
+.CodeMirror-cursor {
+  position: absolute;
+  pointer-events: none;
+}
+.CodeMirror-measure pre { position: static; }
+
+div.CodeMirror-cursors {
+  visibility: hidden;
+  position: relative;
+  z-index: 3;
+}
+div.CodeMirror-dragcursors {
+  visibility: visible;
+}
+
+.CodeMirror-focused div.CodeMirror-cursors {
+  visibility: visible;
+}
+
+.CodeMirror-selected { background: #d9d9d9; }
+.CodeMirror-focused .CodeMirror-selected { background: #d7d4f0; }
+.CodeMirror-crosshair { cursor: crosshair; }
+.CodeMirror-line::selection, .CodeMirror-line > span::selection, .CodeMirror-line > span > span::selection { background: #d7d4f0; }
+.CodeMirror-line::-moz-selection, .CodeMirror-line > span::-moz-selection, .CodeMirror-line > span > span::-moz-selection { background: #d7d4f0; }
+
+.cm-searching {
+  background-color: #ffa;
+  background-color: rgba(255, 255, 0, .4);
+}
+
+/* Used to force a border model for a node */
+.cm-force-border { padding-right: .1px; }
+
+@media print {
+  /* Hide the cursor when printing */
+  .CodeMirror div.CodeMirror-cursors {
+    visibility: hidden;
+  }
+}
+
+/* See issue #2901 */
+.cm-tab-wrap-hack:after { content: ''; }
+
+/* Help users use markselection to safely style text background */
+span.CodeMirror-selectedtext { background: none; }

+ 1 - 1
services/web/webpack.config.js

@@ -4,7 +4,7 @@ module.exports = {
 	// Defines the "entry point(s)" for the application - i.e. the file which
 	// bootstraps the application
 	entry: {
-		'rich-text': './public/es/rich-text.js'
+		richText: './public/es/rich-text.js'
 	},
 
 	// Define where and how the bundle will be output to disk