diff --git a/Gemfile b/Gemfile
index 64724b9408..54cb2d6da7 100644
--- a/Gemfile
+++ b/Gemfile
@@ -63,7 +63,7 @@ gem 'charlock_holmes'
gem 'edtf'
gem 'edtf-humanize'
-
+gem 'diffy'
gem 'terser'
group :assets do
gem 'uglifier'
diff --git a/Gemfile.lock b/Gemfile.lock
index e1b1936228..5084dbb015 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -202,6 +202,7 @@ GEM
devise (>= 4.7.0)
railties (>= 5.2.0)
diff-lcs (1.5.0)
+ diffy (3.4.2)
docile (1.4.0)
easy_translate (0.5.1)
thread
@@ -632,6 +633,7 @@ DEPENDENCIES
devise
devise-encryptable
devise_masquerade (~> 1.2.0)
+ diffy
easy_translate
edtf
edtf-humanize
diff --git a/app/assets/javascripts/application.js b/app/assets/javascripts/application.js
index 0bf13b1cbd..a380e77991 100644
--- a/app/assets/javascripts/application.js
+++ b/app/assets/javascripts/application.js
@@ -319,6 +319,7 @@ function addOptions(selector, enabled_index){
var parentTr = selector.parentElement.parentElement;
var optionsObj = $(parentTr).find('td .field-options')[0];
var index = selector.options.selectedIndex;
+ logger.debug("addOptions");
if (index == enabled_index){
$(optionsObj).prop('disabled', false);
} else {
diff --git a/app/assets/javascripts/codemirror/lib/codemirror.js b/app/assets/javascripts/codemirror/lib/codemirror.js
index 0436a99683..7687cd0538 100644
--- a/app/assets/javascripts/codemirror/lib/codemirror.js
+++ b/app/assets/javascripts/codemirror/lib/codemirror.js
@@ -1,7 +1,7 @@
// CodeMirror, copyright (c) by Marijn Haverbeke and others
-// Distributed under an MIT license: https://codemirror.net/LICENSE
+// Distributed under an MIT license: https://codemirror.net/5/LICENSE
-// This is CodeMirror (https://codemirror.net), a code editor
+// This is CodeMirror (https://codemirror.net/5), a code editor
// implemented in JavaScript on top of the browser's DOM.
//
// You can find some technical background for some of the code below
@@ -26,13 +26,14 @@
var ie_version = ie && (ie_upto10 ? document.documentMode || 6 : +(edge || ie_11up)[1]);
var webkit = !edge && /WebKit\//.test(userAgent);
var qtwebkit = webkit && /Qt\/\d+\.\d+/.test(userAgent);
- var chrome = !edge && /Chrome\//.test(userAgent);
+ var chrome = !edge && /Chrome\/(\d+)/.exec(userAgent);
+ var chrome_version = chrome && +chrome[1];
var presto = /Opera\//.test(userAgent);
var safari = /Apple Computer/.test(navigator.vendor);
var mac_geMountainLion = /Mac OS X 1\d\D([8-9]|\d\d)\D/.test(userAgent);
var phantom = /PhantomJS/.test(userAgent);
- var ios = !edge && /AppleWebKit/.test(userAgent) && /Mobile\/\w+/.test(userAgent);
+ var ios = safari && (/Mobile\/\w+/.test(userAgent) || navigator.maxTouchPoints > 2);
var android = /Android/.test(userAgent);
// This is woefully incomplete. Suggestions for alternative methods welcome.
var mobile = ios || android || /webOS|BlackBerry|Opera Mini|Opera Mobi|IEMobile/i.test(userAgent);
@@ -111,15 +112,16 @@
} while (child = child.parentNode)
}
- function activeElt() {
+ function activeElt(rootNode) {
// IE and Edge may throw an "Unspecified Error" when accessing document.activeElement.
// IE < 10 will throw when accessed while the page is loading or in an iframe.
// IE > 9 and Edge will throw when accessed in an iframe if document.body is unavailable.
+ var doc = rootNode.ownerDocument || rootNode;
var activeElement;
try {
- activeElement = document.activeElement;
+ activeElement = rootNode.activeElement;
} catch(e) {
- activeElement = document.body || null;
+ activeElement = doc.body || null;
}
while (activeElement && activeElement.shadowRoot && activeElement.shadowRoot.activeElement)
{ activeElement = activeElement.shadowRoot.activeElement; }
@@ -143,6 +145,19 @@
else if (ie) // Suppress mysterious IE10 errors
{ selectInput = function(node) { try { node.select(); } catch(_e) {} }; }
+ function doc(cm) { return cm.display.wrapper.ownerDocument }
+
+ function root(cm) {
+ return rootNode(cm.display.wrapper)
+ }
+
+ function rootNode(element) {
+ // Detect modern browsers (2017+).
+ return element.getRootNode ? element.getRootNode() : element.ownerDocument
+ }
+
+ function win(cm) { return doc(cm).defaultView }
+
function bind(f) {
var args = Array.prototype.slice.call(arguments, 1);
return function(){return f.apply(null, args)}
@@ -1311,6 +1326,7 @@
if (span.marker == marker) { return span }
} }
}
+
// Remove a span from an array, returning undefined if no spans are
// left (we don't store arrays for lines without spans).
function removeMarkedSpan(spans, span) {
@@ -1319,9 +1335,16 @@
{ if (spans[i] != span) { (r || (r = [])).push(spans[i]); } }
return r
}
+
// Add a span to a line.
- function addMarkedSpan(line, span) {
- line.markedSpans = line.markedSpans ? line.markedSpans.concat([span]) : [span];
+ function addMarkedSpan(line, span, op) {
+ var inThisOp = op && window.WeakSet && (op.markedSpans || (op.markedSpans = new WeakSet));
+ if (inThisOp && line.markedSpans && inThisOp.has(line.markedSpans)) {
+ line.markedSpans.push(span);
+ } else {
+ line.markedSpans = line.markedSpans ? line.markedSpans.concat([span]) : [span];
+ if (inThisOp) { inThisOp.add(line.markedSpans); }
+ }
span.marker.attachLine(line);
}
@@ -2186,6 +2209,7 @@
if (cm.options.lineNumbers || markers) {
var wrap$1 = ensureLineWrapped(lineView);
var gutterWrap = lineView.gutter = elt("div", null, "CodeMirror-gutter-wrapper", ("left: " + (cm.options.fixedGutter ? dims.fixedPos : -dims.gutterTotalWidth) + "px"));
+ gutterWrap.setAttribute("aria-hidden", "true");
cm.display.input.setUneditable(gutterWrap);
wrap$1.insertBefore(gutterWrap, lineView.text);
if (lineView.line.gutterClass)
@@ -2342,12 +2366,14 @@
function mapFromLineView(lineView, line, lineN) {
if (lineView.line == line)
{ return {map: lineView.measure.map, cache: lineView.measure.cache} }
- for (var i = 0; i < lineView.rest.length; i++)
- { if (lineView.rest[i] == line)
- { return {map: lineView.measure.maps[i], cache: lineView.measure.caches[i]} } }
- for (var i$1 = 0; i$1 < lineView.rest.length; i$1++)
- { if (lineNo(lineView.rest[i$1]) > lineN)
- { return {map: lineView.measure.maps[i$1], cache: lineView.measure.caches[i$1], before: true} } }
+ if (lineView.rest) {
+ for (var i = 0; i < lineView.rest.length; i++)
+ { if (lineView.rest[i] == line)
+ { return {map: lineView.measure.maps[i], cache: lineView.measure.caches[i]} } }
+ for (var i$1 = 0; i$1 < lineView.rest.length; i$1++)
+ { if (lineNo(lineView.rest[i$1]) > lineN)
+ { return {map: lineView.measure.maps[i$1], cache: lineView.measure.caches[i$1], before: true} } }
+ }
}
// Render a line into the hidden node display.externalMeasured. Used
@@ -2561,22 +2587,24 @@
cm.display.lineNumChars = null;
}
- function pageScrollX() {
+ function pageScrollX(doc) {
// Work around https://bugs.chromium.org/p/chromium/issues/detail?id=489206
// which causes page_Offset and bounding client rects to use
// different reference viewports and invalidate our calculations.
- if (chrome && android) { return -(document.body.getBoundingClientRect().left - parseInt(getComputedStyle(document.body).marginLeft)) }
- return window.pageXOffset || (document.documentElement || document.body).scrollLeft
+ if (chrome && android) { return -(doc.body.getBoundingClientRect().left - parseInt(getComputedStyle(doc.body).marginLeft)) }
+ return doc.defaultView.pageXOffset || (doc.documentElement || doc.body).scrollLeft
}
- function pageScrollY() {
- if (chrome && android) { return -(document.body.getBoundingClientRect().top - parseInt(getComputedStyle(document.body).marginTop)) }
- return window.pageYOffset || (document.documentElement || document.body).scrollTop
+ function pageScrollY(doc) {
+ if (chrome && android) { return -(doc.body.getBoundingClientRect().top - parseInt(getComputedStyle(doc.body).marginTop)) }
+ return doc.defaultView.pageYOffset || (doc.documentElement || doc.body).scrollTop
}
function widgetTopHeight(lineObj) {
+ var ref = visualLine(lineObj);
+ var widgets = ref.widgets;
var height = 0;
- if (lineObj.widgets) { for (var i = 0; i < lineObj.widgets.length; ++i) { if (lineObj.widgets[i].above)
- { height += widgetHeight(lineObj.widgets[i]); } } }
+ if (widgets) { for (var i = 0; i < widgets.length; ++i) { if (widgets[i].above)
+ { height += widgetHeight(widgets[i]); } } }
return height
}
@@ -2596,8 +2624,8 @@
else { yOff -= cm.display.viewOffset; }
if (context == "page" || context == "window") {
var lOff = cm.display.lineSpace.getBoundingClientRect();
- yOff += lOff.top + (context == "window" ? 0 : pageScrollY());
- var xOff = lOff.left + (context == "window" ? 0 : pageScrollX());
+ yOff += lOff.top + (context == "window" ? 0 : pageScrollY(doc(cm)));
+ var xOff = lOff.left + (context == "window" ? 0 : pageScrollX(doc(cm)));
rect.left += xOff; rect.right += xOff;
}
rect.top += yOff; rect.bottom += yOff;
@@ -2611,8 +2639,8 @@
var left = coords.left, top = coords.top;
// First move into "page" coordinate system
if (context == "page") {
- left -= pageScrollX();
- top -= pageScrollY();
+ left -= pageScrollX(doc(cm));
+ top -= pageScrollY(doc(cm));
} else if (context == "local" || !context) {
var localBox = cm.display.sizer.getBoundingClientRect();
left += localBox.left;
@@ -3141,13 +3169,19 @@
var curFragment = result.cursors = document.createDocumentFragment();
var selFragment = result.selection = document.createDocumentFragment();
+ var customCursor = cm.options.$customCursor;
+ if (customCursor) { primary = true; }
for (var i = 0; i < doc.sel.ranges.length; i++) {
if (!primary && i == doc.sel.primIndex) { continue }
var range = doc.sel.ranges[i];
if (range.from().line >= cm.display.viewTo || range.to().line < cm.display.viewFrom) { continue }
var collapsed = range.empty();
- if (collapsed || cm.options.showCursorWhenSelecting)
- { drawSelectionCursor(cm, range.head, curFragment); }
+ if (customCursor) {
+ var head = customCursor(cm, range);
+ if (head) { drawSelectionCursor(cm, head, curFragment); }
+ } else if (collapsed || cm.options.showCursorWhenSelecting) {
+ drawSelectionCursor(cm, range.head, curFragment);
+ }
if (!collapsed)
{ drawSelectionRange(cm, range, selFragment); }
}
@@ -3163,6 +3197,12 @@
cursor.style.top = pos.top + "px";
cursor.style.height = Math.max(0, pos.bottom - pos.top) * cm.options.cursorHeight + "px";
+ if (/\bcm-fat-cursor\b/.test(cm.getWrapperElement().className)) {
+ var charPos = charCoords(cm, head, "div", null, null);
+ var width = charPos.right - charPos.left;
+ cursor.style.width = (width > 0 ? width : cm.defaultCharWidth()) + "px";
+ }
+
if (pos.other) {
// Secondary cursor, shown when on a 'jump' in bi-directional text
var otherCursor = output.appendChild(elt("div", "\u00a0", "CodeMirror-cursor CodeMirror-secondarycursor"));
@@ -3335,10 +3375,14 @@
function updateHeightsInViewport(cm) {
var display = cm.display;
var prevBottom = display.lineDiv.offsetTop;
+ var viewTop = Math.max(0, display.scroller.getBoundingClientRect().top);
+ var oldHeight = display.lineDiv.getBoundingClientRect().top;
+ var mustScroll = 0;
for (var i = 0; i < display.view.length; i++) {
var cur = display.view[i], wrapping = cm.options.lineWrapping;
var height = (void 0), width = 0;
if (cur.hidden) { continue }
+ oldHeight += cur.line.height;
if (ie && ie_version < 8) {
var bot = cur.node.offsetTop + cur.node.offsetHeight;
height = bot - prevBottom;
@@ -3353,6 +3397,7 @@
}
var diff = cur.line.height - height;
if (diff > .005 || diff < -.005) {
+ if (oldHeight < viewTop) { mustScroll -= diff; }
updateLineHeight(cur.line, height);
updateWidgetHeight(cur.line);
if (cur.rest) { for (var j = 0; j < cur.rest.length; j++)
@@ -3367,6 +3412,7 @@
}
}
}
+ if (Math.abs(mustScroll) > 2) { display.scroller.scrollTop += mustScroll; }
}
// Read and store the height of line widgets associated with the
@@ -3410,8 +3456,9 @@
if (signalDOMEvent(cm, "scrollCursorIntoView")) { return }
var display = cm.display, box = display.sizer.getBoundingClientRect(), doScroll = null;
+ var doc = display.wrapper.ownerDocument;
if (rect.top + box.top < 0) { doScroll = true; }
- else if (rect.bottom + box.top > (window.innerHeight || document.documentElement.clientHeight)) { doScroll = false; }
+ else if (rect.bottom + box.top > (doc.defaultView.innerHeight || doc.documentElement.clientHeight)) { doScroll = false; }
if (doScroll != null && !phantom) {
var scrollNode = elt("div", "\u200b", null, ("position: absolute;\n top: " + (rect.top - display.viewOffset - paddingTop(cm.display)) + "px;\n height: " + (rect.bottom - rect.top + scrollGap(cm) + display.barHeight) + "px;\n left: " + (rect.left) + "px; width: " + (Math.max(2, rect.right - rect.left)) + "px;"));
cm.display.lineSpace.appendChild(scrollNode);
@@ -3430,8 +3477,8 @@
// Set pos and end to the cursor positions around the character pos sticks to
// If pos.sticky == "before", that is around pos.ch - 1, otherwise around pos.ch
// If pos == Pos(_, 0, "before"), pos and end are unchanged
- pos = pos.ch ? Pos(pos.line, pos.sticky == "before" ? pos.ch - 1 : pos.ch, "after") : pos;
end = pos.sticky == "before" ? Pos(pos.line, pos.ch + 1, "before") : pos;
+ pos = pos.ch ? Pos(pos.line, pos.sticky == "before" ? pos.ch - 1 : pos.ch, "after") : pos;
}
for (var limit = 0; limit < 5; limit++) {
var changed = false;
@@ -3627,6 +3674,7 @@
this.vert.firstChild.style.height =
Math.max(0, measure.scrollHeight - measure.clientHeight + totalHeight) + "px";
} else {
+ this.vert.scrollTop = 0;
this.vert.style.display = "";
this.vert.firstChild.style.height = "0";
}
@@ -3664,13 +3712,13 @@
NativeScrollbars.prototype.zeroWidthHack = function () {
var w = mac && !mac_geMountainLion ? "12px" : "18px";
this.horiz.style.height = this.vert.style.width = w;
- this.horiz.style.pointerEvents = this.vert.style.pointerEvents = "none";
+ this.horiz.style.visibility = this.vert.style.visibility = "hidden";
this.disableHoriz = new Delayed;
this.disableVert = new Delayed;
};
NativeScrollbars.prototype.enableZeroWidthBar = function (bar, delay, type) {
- bar.style.pointerEvents = "auto";
+ bar.style.visibility = "";
function maybeDisable() {
// To find out whether the scrollbar is still visible, we
// check whether the element under the pixel in the bottom
@@ -3681,7 +3729,7 @@
var box = bar.getBoundingClientRect();
var elt = type == "vert" ? document.elementFromPoint(box.right - 1, (box.top + box.bottom) / 2)
: document.elementFromPoint((box.right + box.left) / 2, box.bottom - 1);
- if (elt != bar) { bar.style.pointerEvents = "none"; }
+ if (elt != bar) { bar.style.visibility = "hidden"; }
else { delay.set(1000, maybeDisable); }
}
delay.set(1000, maybeDisable);
@@ -3782,7 +3830,8 @@
scrollLeft: null, scrollTop: null, // Intermediate scroll position, not pushed to DOM yet
scrollToPos: null, // Used to scroll to a specific position
focus: false,
- id: ++nextOpId // Unique ID
+ id: ++nextOpId, // Unique ID
+ markArrays: null // Used by addMarkedSpan
};
pushOperation(cm.curOp);
}
@@ -3861,7 +3910,7 @@
cm.display.maxLineChanged = false;
}
- var takeFocus = op.focus && op.focus == activeElt();
+ var takeFocus = op.focus && op.focus == activeElt(root(cm));
if (op.preparedSelection)
{ cm.display.input.showSelection(op.preparedSelection, takeFocus); }
if (op.updatedDisplay || op.startHeight != cm.doc.height)
@@ -4038,11 +4087,11 @@
function selectionSnapshot(cm) {
if (cm.hasFocus()) { return null }
- var active = activeElt();
+ var active = activeElt(root(cm));
if (!active || !contains(cm.display.lineDiv, active)) { return null }
var result = {activeElt: active};
if (window.getSelection) {
- var sel = window.getSelection();
+ var sel = win(cm).getSelection();
if (sel.anchorNode && sel.extend && contains(cm.display.lineDiv, sel.anchorNode)) {
result.anchorNode = sel.anchorNode;
result.anchorOffset = sel.anchorOffset;
@@ -4054,11 +4103,12 @@
}
function restoreSelection(snapshot) {
- if (!snapshot || !snapshot.activeElt || snapshot.activeElt == activeElt()) { return }
+ if (!snapshot || !snapshot.activeElt || snapshot.activeElt == activeElt(rootNode(snapshot.activeElt))) { return }
snapshot.activeElt.focus();
if (!/^(INPUT|TEXTAREA)$/.test(snapshot.activeElt.nodeName) &&
snapshot.anchorNode && contains(document.body, snapshot.anchorNode) && contains(document.body, snapshot.focusNode)) {
- var sel = window.getSelection(), range = document.createRange();
+ var doc = snapshot.activeElt.ownerDocument;
+ var sel = doc.defaultView.getSelection(), range = doc.createRange();
range.setEnd(snapshot.anchorNode, snapshot.anchorOffset);
range.collapse(false);
sel.removeAllRanges();
@@ -4235,6 +4285,8 @@
function updateGutterSpace(display) {
var width = display.gutters.offsetWidth;
display.sizer.style.marginLeft = width + "px";
+ // Send an event to consumers responding to changes in gutter width.
+ signalLater(display, "gutterChanged", display);
}
function setDocumentHeight(cm, measure) {
@@ -4373,6 +4425,12 @@
d.scroller.setAttribute("tabIndex", "-1");
// The element in which the editor lives.
d.wrapper = elt("div", [d.scrollbarFiller, d.gutterFiller, d.scroller], "CodeMirror");
+ // See #6982. FIXME remove when this has been fixed for a while in Chrome
+ if (chrome && chrome_version >= 105) { d.wrapper.style.clipPath = "inset(0px)"; }
+
+ // This attribute is respected by automatic translation systems such as Google Translate,
+ // and may also be respected by tools used by human translators.
+ d.wrapper.setAttribute('translate', 'no');
// Work around IE7 z-index bug (not perfect, hence IE7 not really being supported)
if (ie && ie_version < 8) { d.gutters.style.zIndex = -1; d.scroller.style.paddingRight = 0; }
@@ -4470,7 +4528,24 @@
}
function onScrollWheel(cm, e) {
+ // On Chrome 102, viewport updates somehow stop wheel-based
+ // scrolling. Turning off pointer events during the scroll seems
+ // to avoid the issue.
+ if (chrome && chrome_version == 102) {
+ if (cm.display.chromeScrollHack == null) { cm.display.sizer.style.pointerEvents = "none"; }
+ else { clearTimeout(cm.display.chromeScrollHack); }
+ cm.display.chromeScrollHack = setTimeout(function () {
+ cm.display.chromeScrollHack = null;
+ cm.display.sizer.style.pointerEvents = "";
+ }, 100);
+ }
var delta = wheelEventDelta(e), dx = delta.x, dy = delta.y;
+ var pixelsPerUnit = wheelPixelsPerUnit;
+ if (e.deltaMode === 0) {
+ dx = e.deltaX;
+ dy = e.deltaY;
+ pixelsPerUnit = 1;
+ }
var display = cm.display, scroll = display.scroller;
// Quit if there's nothing to scroll here
@@ -4499,10 +4574,10 @@
// estimated pixels/delta value, we just handle horizontal
// scrolling entirely here. It'll be slightly off from native, but
// better than glitching out.
- if (dx && !gecko && !presto && wheelPixelsPerUnit != null) {
+ if (dx && !gecko && !presto && pixelsPerUnit != null) {
if (dy && canScrollY)
- { updateScrollTop(cm, Math.max(0, scroll.scrollTop + dy * wheelPixelsPerUnit)); }
- setScrollLeft(cm, Math.max(0, scroll.scrollLeft + dx * wheelPixelsPerUnit));
+ { updateScrollTop(cm, Math.max(0, scroll.scrollTop + dy * pixelsPerUnit)); }
+ setScrollLeft(cm, Math.max(0, scroll.scrollLeft + dx * pixelsPerUnit));
// Only prevent default scrolling if vertical scrolling is
// actually possible. Otherwise, it causes vertical scroll
// jitter on OSX trackpads when deltaX is small and deltaY
@@ -4515,15 +4590,15 @@
// 'Project' the visible viewport to cover the area that is being
// scrolled into view (if we know enough to estimate it).
- if (dy && wheelPixelsPerUnit != null) {
- var pixels = dy * wheelPixelsPerUnit;
+ if (dy && pixelsPerUnit != null) {
+ var pixels = dy * pixelsPerUnit;
var top = cm.doc.scrollTop, bot = top + display.wrapper.clientHeight;
if (pixels < 0) { top = Math.max(0, top + pixels - 50); }
else { bot = Math.min(cm.doc.height, bot + pixels + 50); }
updateDisplaySimple(cm, {top: top, bottom: bot});
}
- if (wheelSamples < 20) {
+ if (wheelSamples < 20 && e.deltaMode !== 0) {
if (display.wheelStartX == null) {
display.wheelStartX = scroll.scrollLeft; display.wheelStartY = scroll.scrollTop;
display.wheelDX = dx; display.wheelDY = dy;
@@ -4782,6 +4857,7 @@
estimateLineHeights(cm);
loadMode(cm);
setDirectionClass(cm);
+ cm.options.direction = doc.direction;
if (!cm.options.lineWrapping) { findMaxLine(cm); }
cm.options.mode = doc.modeOption;
regChange(cm);
@@ -4798,19 +4874,19 @@
});
}
- function History(startGen) {
+ function History(prev) {
// Arrays of change events and selections. Doing something adds an
// event to done and clears undo. Undoing moves events from done
// to undone, redoing moves them in the other direction.
this.done = []; this.undone = [];
- this.undoDepth = Infinity;
+ this.undoDepth = prev ? prev.undoDepth : Infinity;
// Used to track when changes can be merged into a single undo
// event
this.lastModTime = this.lastSelTime = 0;
this.lastOp = this.lastSelOp = null;
this.lastOrigin = this.lastSelOrigin = null;
// Used by the isClean() method
- this.generation = this.maxGeneration = startGen || 1;
+ this.generation = this.maxGeneration = prev ? prev.maxGeneration : 1;
}
// Create a history change event from an updateDoc-style change
@@ -5115,7 +5191,7 @@
(cmp(sel.primary().head, doc.sel.primary().head) < 0 ? -1 : 1);
setSelectionInner(doc, skipAtomicInSelection(doc, sel, bias, true));
- if (!(options && options.scroll === false) && doc.cm)
+ if (!(options && options.scroll === false) && doc.cm && doc.cm.getOption("readOnly") != "nocursor")
{ ensureCursorVisible(doc.cm); }
}
@@ -5146,7 +5222,7 @@
var range = sel.ranges[i];
var old = sel.ranges.length == doc.sel.ranges.length && doc.sel.ranges[i];
var newAnchor = skipAtomic(doc, range.anchor, old && old.anchor, bias, mayClear);
- var newHead = skipAtomic(doc, range.head, old && old.head, bias, mayClear);
+ var newHead = range.head == range.anchor ? newAnchor : skipAtomic(doc, range.head, old && old.head, bias, mayClear);
if (out || newAnchor != range.anchor || newHead != range.head) {
if (!out) { out = sel.ranges.slice(0, i); }
out[i] = new Range(newAnchor, newHead);
@@ -5958,7 +6034,7 @@
if (marker.collapsed && curLine != from.line) { updateLineHeight(line, 0); }
addMarkedSpan(line, new MarkedSpan(marker,
curLine == from.line ? from.ch : null,
- curLine == to.line ? to.ch : null));
+ curLine == to.line ? to.ch : null), doc.cm && doc.cm.curOp);
++curLine;
});
// lineIsHidden depends on the presence of the spans, so needs a second pass
@@ -6130,6 +6206,7 @@
getRange: function(from, to, lineSep) {
var lines = getBetween(this, clipPos(this, from), clipPos(this, to));
if (lineSep === false) { return lines }
+ if (lineSep === '') { return lines.join('') }
return lines.join(lineSep || this.lineSeparator())
},
@@ -6181,7 +6258,7 @@
var out = [];
for (var i = 0; i < ranges.length; i++)
{ out[i] = new Range(clipPos(this, ranges[i].anchor),
- clipPos(this, ranges[i].head)); }
+ clipPos(this, ranges[i].head || ranges[i].anchor)); }
if (primary == null) { primary = Math.min(ranges.length - 1, this.sel.primIndex); }
setSelection(this, normalizeSelection(this.cm, out, primary), options);
}),
@@ -6244,7 +6321,7 @@
clearHistory: function() {
var this$1 = this;
- this.history = new History(this.history.maxGeneration);
+ this.history = new History(this.history);
linkedDocs(this, function (doc) { return doc.history = this$1.history; }, true);
},
@@ -6265,7 +6342,7 @@
undone: copyHistoryArray(this.history.undone)}
},
setHistory: function(histData) {
- var hist = this.history = new History(this.history.maxGeneration);
+ var hist = this.history = new History(this.history);
hist.done = copyHistoryArray(histData.done.slice(0), null, true);
hist.undone = copyHistoryArray(histData.undone.slice(0), null, true);
},
@@ -6684,10 +6761,9 @@
// Very basic readline/emacs-style bindings, which are standard on Mac.
keyMap.emacsy = {
"Ctrl-F": "goCharRight", "Ctrl-B": "goCharLeft", "Ctrl-P": "goLineUp", "Ctrl-N": "goLineDown",
- "Alt-F": "goWordRight", "Alt-B": "goWordLeft", "Ctrl-A": "goLineStart", "Ctrl-E": "goLineEnd",
- "Ctrl-V": "goPageDown", "Shift-Ctrl-V": "goPageUp", "Ctrl-D": "delCharAfter", "Ctrl-H": "delCharBefore",
- "Alt-D": "delWordAfter", "Alt-Backspace": "delWordBefore", "Ctrl-K": "killLine", "Ctrl-T": "transposeChars",
- "Ctrl-O": "openLine"
+ "Ctrl-A": "goLineStart", "Ctrl-E": "goLineEnd", "Ctrl-V": "goPageDown", "Shift-Ctrl-V": "goPageUp",
+ "Ctrl-D": "delCharAfter", "Ctrl-H": "delCharBefore", "Alt-Backspace": "delWordBefore", "Ctrl-K": "killLine",
+ "Ctrl-T": "transposeChars", "Ctrl-O": "openLine"
};
keyMap.macDefault = {
"Cmd-A": "selectAll", "Cmd-D": "deleteLine", "Cmd-Z": "undo", "Shift-Cmd-Z": "redo", "Cmd-Y": "redo",
@@ -7198,7 +7274,7 @@
function onKeyDown(e) {
var cm = this;
if (e.target && e.target != cm.display.input.getField()) { return }
- cm.curOp.focus = activeElt();
+ cm.curOp.focus = activeElt(root(cm));
if (signalDOMEvent(cm, e)) { return }
// IE does strange things with escape.
if (ie && ie_version < 11 && e.keyCode == 27) { e.returnValue = false; }
@@ -7305,7 +7381,7 @@
}
if (clickInGutter(cm, e)) { return }
var pos = posFromMouse(cm, e), button = e_button(e), repeat = pos ? clickRepeat(pos, button) : "single";
- window.focus();
+ win(cm).focus();
// #3261: make sure, that we're not starting a second selection
if (button == 1 && cm.state.selectingText)
@@ -7360,7 +7436,7 @@
function leftButtonDown(cm, pos, repeat, event) {
if (ie) { setTimeout(bind(ensureFocus, cm), 0); }
- else { cm.curOp.focus = activeElt(); }
+ else { cm.curOp.focus = activeElt(root(cm)); }
var behavior = configureMouse(cm, repeat, event);
@@ -7524,7 +7600,7 @@
var cur = posFromMouse(cm, e, true, behavior.unit == "rectangle");
if (!cur) { return }
if (cmp(cur, lastPos) != 0) {
- cm.curOp.focus = activeElt();
+ cm.curOp.focus = activeElt(root(cm));
extendTo(cur);
var visible = visibleLines(display, doc);
if (cur.line >= visible.to || cur.line < visible.from)
@@ -7708,7 +7784,7 @@
for (var i = newBreaks.length - 1; i >= 0; i--)
{ replaceRange(cm.doc, val, newBreaks[i], Pos(newBreaks[i].line, newBreaks[i].ch + val.length)); }
});
- option("specialChars", /[\u0000-\u001f\u007f-\u009f\u00ad\u061c\u200b-\u200c\u200e\u200f\u2028\u2029\ufeff\ufff9-\ufffc]/g, function (cm, val, old) {
+ option("specialChars", /[\u0000-\u001f\u007f-\u009f\u00ad\u061c\u200b\u200e\u200f\u2028\u2029\u202d\u202e\u2066\u2067\u2069\ufeff\ufff9-\ufffc]/g, function (cm, val, old) {
cm.state.specialChars = new RegExp(val.source + (val.test("\t") ? "" : "|\t"), "g");
if (old != Init) { cm.refresh(); }
});
@@ -8151,7 +8227,7 @@
var pasted = e.clipboardData && e.clipboardData.getData("Text");
if (pasted) {
e.preventDefault();
- if (!cm.isReadOnly() && !cm.options.disableInput)
+ if (!cm.isReadOnly() && !cm.options.disableInput && cm.hasFocus())
{ runInOp(cm, function () { return applyTextInput(cm, pasted, 0, null, "paste"); }); }
return true
}
@@ -8193,13 +8269,13 @@
}
function disableBrowserMagic(field, spellcheck, autocorrect, autocapitalize) {
- field.setAttribute("autocorrect", autocorrect ? "" : "off");
- field.setAttribute("autocapitalize", autocapitalize ? "" : "off");
+ field.setAttribute("autocorrect", autocorrect ? "on" : "off");
+ field.setAttribute("autocapitalize", autocapitalize ? "on" : "off");
field.setAttribute("spellcheck", !!spellcheck);
}
function hiddenTextarea() {
- var te = elt("textarea", null, null, "position: absolute; bottom: -1em; padding: 0; width: 1px; height: 1em; outline: none");
+ var te = elt("textarea", null, null, "position: absolute; bottom: -1em; padding: 0; width: 1px; height: 1em; min-height: 1em; outline: none");
var div = elt("div", [te], null, "overflow: hidden; position: relative; width: 3px; height: 0px;");
// The textarea is kept positioned near the cursor to prevent the
// fact that it'll be scrolled into view on input from scrolling
@@ -8209,7 +8285,6 @@
else { te.setAttribute("wrap", "off"); }
// If border: 0; -- iOS fails to open keyboard (issue #1287)
if (ios) { te.style.border = "1px solid black"; }
- disableBrowserMagic(te);
return div
}
@@ -8228,7 +8303,7 @@
CodeMirror.prototype = {
constructor: CodeMirror,
- focus: function(){window.focus(); this.display.input.focus();},
+ focus: function(){win(this).focus(); this.display.input.focus();},
setOption: function(option, value) {
var options = this.options, old = options[option];
@@ -8552,7 +8627,7 @@
signal(this, "overwriteToggle", this, this.state.overwrite);
},
- hasFocus: function() { return this.display.input.getField() == activeElt() },
+ hasFocus: function() { return this.display.input.getField() == activeElt(root(this)) },
isReadOnly: function() { return !!(this.options.readOnly || this.doc.cantEdit) },
scrollTo: methodOp(function (x, y) { scrollToCoords(this, x, y); }),
@@ -8675,10 +8750,13 @@
function moveOnce(boundToLine) {
var next;
if (unit == "codepoint") {
- var ch = lineObj.text.charCodeAt(pos.ch + (unit > 0 ? 0 : -1));
- if (isNaN(ch)) { next = null; }
- else { next = new Pos(pos.line, Math.max(0, Math.min(lineObj.text.length, pos.ch + dir * (ch >= 0xD800 && ch < 0xDC00 ? 2 : 1))),
- -dir); }
+ var ch = lineObj.text.charCodeAt(pos.ch + (dir > 0 ? 0 : -1));
+ if (isNaN(ch)) {
+ next = null;
+ } else {
+ var astral = dir > 0 ? ch >= 0xD800 && ch < 0xDC00 : ch >= 0xDC00 && ch < 0xDFFF;
+ next = new Pos(pos.line, Math.max(0, Math.min(lineObj.text.length, pos.ch + dir * (astral ? 2 : 1))), -dir);
+ }
} else if (visually) {
next = moveVisually(doc.cm, lineObj, pos, dir);
} else {
@@ -8730,7 +8808,7 @@
function findPosV(cm, pos, dir, unit) {
var doc = cm.doc, x = pos.left, y;
if (unit == "page") {
- var pageSize = Math.min(cm.display.wrapper.clientHeight, window.innerHeight || document.documentElement.clientHeight);
+ var pageSize = Math.min(cm.display.wrapper.clientHeight, win(cm).innerHeight || doc(cm).documentElement.clientHeight);
var moveAmount = Math.max(pageSize - .5 * textHeight(cm.display), 3);
y = (dir > 0 ? pos.bottom : pos.top) + dir * moveAmount;
@@ -8763,6 +8841,7 @@
var input = this, cm = input.cm;
var div = input.div = display.lineDiv;
+ div.contentEditable = true;
disableBrowserMagic(div, cm.options.spellcheck, cm.options.autocorrect, cm.options.autocapitalize);
function belongsToInput(e) {
@@ -8827,9 +8906,10 @@
}
// Old-fashioned briefly-focus-a-textarea hack
var kludge = hiddenTextarea(), te = kludge.firstChild;
+ disableBrowserMagic(te);
cm.display.lineSpace.insertBefore(kludge, cm.display.lineSpace.firstChild);
te.value = lastCopied.text.join("\n");
- var hadFocus = document.activeElement;
+ var hadFocus = activeElt(rootNode(div));
selectInput(te);
setTimeout(function () {
cm.display.lineSpace.removeChild(kludge);
@@ -8852,7 +8932,7 @@
ContentEditableInput.prototype.prepareSelection = function () {
var result = prepareSelection(this.cm, false);
- result.focus = document.activeElement == this.div;
+ result.focus = activeElt(rootNode(this.div)) == this.div;
return result
};
@@ -8948,7 +9028,7 @@
ContentEditableInput.prototype.focus = function () {
if (this.cm.options.readOnly != "nocursor") {
- if (!this.selectionInEditor() || document.activeElement != this.div)
+ if (!this.selectionInEditor() || activeElt(rootNode(this.div)) != this.div)
{ this.showSelection(this.prepareSelection(), true); }
this.div.focus();
}
@@ -8959,9 +9039,11 @@
ContentEditableInput.prototype.supportsTouch = function () { return true };
ContentEditableInput.prototype.receivedFocus = function () {
+ var this$1 = this;
+
var input = this;
if (this.selectionInEditor())
- { this.pollSelection(); }
+ { setTimeout(function () { return this$1.pollSelection(); }, 20); }
else
{ runInOp(this.cm, function () { return input.cm.curOp.selectionChanged = true; }); }
@@ -9298,6 +9380,7 @@
// Used to work around IE issue with selection being forgotten when focus moves away from textarea
this.hasSelection = false;
this.composing = null;
+ this.resetting = false;
};
TextareaInput.prototype.init = function (display) {
@@ -9388,6 +9471,8 @@
// The semihidden textarea that is focused when the editor is
// focused, and receives input.
this.textarea = this.wrapper.firstChild;
+ var opts = this.cm.options;
+ disableBrowserMagic(this.textarea, opts.spellcheck, opts.autocorrect, opts.autocapitalize);
};
TextareaInput.prototype.screenReaderLabelChanged = function (label) {
@@ -9430,8 +9515,9 @@
// Reset the input to correspond to the selection (or to be empty,
// when not typing and nothing is selected)
TextareaInput.prototype.reset = function (typing) {
- if (this.contextMenuPending || this.composing) { return }
+ if (this.contextMenuPending || this.composing && typing) { return }
var cm = this.cm;
+ this.resetting = true;
if (cm.somethingSelected()) {
this.prevInput = "";
var content = cm.getSelection();
@@ -9442,6 +9528,7 @@
this.prevInput = this.textarea.value = "";
if (ie && ie_version >= 9) { this.hasSelection = null; }
}
+ this.resetting = false;
};
TextareaInput.prototype.getField = function () { return this.textarea };
@@ -9449,7 +9536,7 @@
TextareaInput.prototype.supportsTouch = function () { return false };
TextareaInput.prototype.focus = function () {
- if (this.cm.options.readOnly != "nocursor" && (!mobile || activeElt() != this.textarea)) {
+ if (this.cm.options.readOnly != "nocursor" && (!mobile || activeElt(rootNode(this.textarea)) != this.textarea)) {
try { this.textarea.focus(); }
catch (e) {} // IE8 will throw if the textarea is display: none or not in DOM
}
@@ -9503,7 +9590,7 @@
// possible when it is clear that nothing happened. hasSelection
// will be the case when there is a lot of text in the textarea,
// in which case reading its value would be expensive.
- if (this.contextMenuPending || !cm.state.focused ||
+ if (this.contextMenuPending || this.resetting || !cm.state.focused ||
(hasSelection(input) && !prevInput && !this.composing) ||
cm.isReadOnly() || cm.options.disableInput || cm.state.keySeq)
{ return false }
@@ -9572,9 +9659,9 @@
input.wrapper.style.cssText = "position: static";
te.style.cssText = "position: absolute; width: 30px; height: 30px;\n top: " + (e.clientY - wrapperBox.top - 5) + "px; left: " + (e.clientX - wrapperBox.left - 5) + "px;\n z-index: 1000; background: " + (ie ? "rgba(255, 255, 255, .05)" : "transparent") + ";\n outline: none; border-width: 0; outline: none; overflow: hidden; opacity: .05; filter: alpha(opacity=5);";
var oldScrollY;
- if (webkit) { oldScrollY = window.scrollY; } // Work around Chrome issue (#2712)
+ if (webkit) { oldScrollY = te.ownerDocument.defaultView.scrollY; } // Work around Chrome issue (#2712)
display.input.focus();
- if (webkit) { window.scrollTo(null, oldScrollY); }
+ if (webkit) { te.ownerDocument.defaultView.scrollTo(null, oldScrollY); }
display.input.reset();
// Adds "Select all" to context menu in FF
if (!cm.somethingSelected()) { te.value = input.prevInput = " "; }
@@ -9656,7 +9743,7 @@
// Set autofocus to true if this textarea is focused, or if it has
// autofocus and no other element is focused.
if (options.autofocus == null) {
- var hasFocus = activeElt();
+ var hasFocus = activeElt(rootNode(textarea));
options.autofocus = hasFocus == textarea ||
textarea.getAttribute("autofocus") != null && hasFocus == document.body;
}
@@ -9790,7 +9877,7 @@
addLegacyProps(CodeMirror);
- CodeMirror.version = "5.58.3";
+ CodeMirror.version = "5.65.16";
return CodeMirror;
diff --git a/app/assets/stylesheets/codemirror/lib/codemirror.css b/app/assets/stylesheets/codemirror/lib/codemirror.css
index 97a9e29150..f4d5718a78 100644
--- a/app/assets/stylesheets/codemirror/lib/codemirror.css
+++ b/app/assets/stylesheets/codemirror/lib/codemirror.css
@@ -3,17 +3,9 @@
.CodeMirror {
/* Set height, width, borders, and global font properties here */
font-family: monospace;
- height: auto;
+ height: 300px;
color: black;
-}
-/* Set the direction of the lenguage for ltr and rtl */
-.rtl {
- text-align: right;
- padding: 20px;
-}
-
-.ltr {
- text-align: left;
+ direction: ltr;
}
/* PADDING */
@@ -68,20 +60,13 @@
.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;
-}
+.cm-fat-cursor .CodeMirror-line::selection,
+.cm-fat-cursor .CodeMirror-line > span::selection,
+.cm-fat-cursor .CodeMirror-line > span > span::selection { background: transparent; }
+.cm-fat-cursor .CodeMirror-line::-moz-selection,
+.cm-fat-cursor .CodeMirror-line > span::-moz-selection,
+.cm-fat-cursor .CodeMirror-line > span > span::-moz-selection { background: transparent; }
+.cm-fat-cursor { caret-color: transparent; }
@-moz-keyframes blink {
0% {}
50% { background-color: transparent; }
@@ -179,6 +164,7 @@ div.CodeMirror span.CodeMirror-nonmatchingbracket {color: #a22;}
height: 100%;
outline: none; /* Prevent dragging from highlighting the element */
position: relative;
+ z-index: 0;
}
.CodeMirror-sizer {
position: relative;
diff --git a/app/models/page.rb b/app/models/page.rb
index 4f751dc5d3..1a7f063a64 100644
--- a/app/models/page.rb
+++ b/app/models/page.rb
@@ -541,19 +541,26 @@ def contributors
users
end
- def has_alto?
- File.exists?(alto_path)
+ def has_ai_plaintext?
+ File.exists?(ai_plaintext_path)
end
- def create_alto
- if File.exists? original_htr_path
- # convert the htr to alto
- # write the alto to the alto path
+ def ai_plaintext
+ if has_ai_plaintext?
+ File.read(ai_plaintext_path)
else
- # call a service to create HTR
- PageProcessor.new(self).submit_process
+ ""
end
+ end
+ def ai_plaintext=(text)
+ FileUtils.mkdir_p(File.dirname(ai_plaintext_path)) unless Dir.exist? File.dirname(ai_plaintext_path)
+ File.write(ai_plaintext_path, text)
+ end
+
+
+ def has_alto?
+ File.exists?(alto_path)
end
@@ -582,6 +589,9 @@ def image_url_for_download
end
private
+ def ai_plaintext_path
+ File.join(Rails.root, 'public', 'text', self.work_id.to_s, "#{self.id}_ai_plaintext.txt")
+ end
def alto_path
File.join(Rails.root, 'public', 'text', self.work_id.to_s, "#{self.id}_alto.xml")
diff --git a/app/views/shared/_codemirror.html.erb b/app/views/shared/_codemirror.html.erb
index 2ef02201f4..eed7206e16 100644
--- a/app/views/shared/_codemirror.html.erb
+++ b/app/views/shared/_codemirror.html.erb
@@ -6,6 +6,26 @@
<%=javascript_include_tag "codemirror/addon/hint/xml-hint"%>
<%=javascript_include_tag "codemirror-buttons/buttons"%>
+<% if @page && !@page.ai_plaintext.blank? %>
+ <%= t('.copy_ai_plaintext') %>
+
+<% end %>
+