Implemented find and replace extension

This commit is contained in:
benweet 2014-07-13 22:45:55 +01:00
parent e01db60b07
commit 82be3a44f2
12 changed files with 782 additions and 348 deletions

View File

@ -117,34 +117,65 @@ define([
this.selectionStart = 0; this.selectionStart = 0;
this.selectionEnd = 0; this.selectionEnd = 0;
this.cursorY = 0; this.cursorY = 0;
this.findOffset = function(offset) { this.adjustTop = 0;
this.adjustBottom = 0;
this.findOffsets = function(offsetList) {
var result = [];
if(!offsetList.length) {
return result;
}
var offset = offsetList.shift();
var walker = document.createTreeWalker(contentElt, 4, null, false); var walker = document.createTreeWalker(contentElt, 4, null, false);
var text = ''; var text = '';
var walkerOffset = 0;
while(walker.nextNode()) { while(walker.nextNode()) {
text = walker.currentNode.nodeValue || ''; text = walker.currentNode.nodeValue || '';
if(text.length > offset) { var newWalkerOffset = walkerOffset + text.length;
return { while(newWalkerOffset > offset) {
result.push({
container: walker.currentNode, container: walker.currentNode,
offsetInContainer: offset - walkerOffset,
offset: offset offset: offset
}; });
if(!offsetList.length) {
return result;
}
offset = offsetList.shift();
} }
offset -= text.length; walkerOffset = newWalkerOffset;
} }
return { do {
container: walker.currentNode, result.push({
offset: text.length container: walker.currentNode,
}; offsetInContainer: walkerOffset,
offset: offset
});
offset = offsetList.shift();
}
while(offset);
return result;
}; };
this.createRange = function(start, end) { this.createRange = function(start, end) {
start = start < 0 ? 0 : start; start = start < 0 ? 0 : start;
end = end < 0 ? 0 : end; end = end < 0 ? 0 : end;
var range = document.createRange(); var range = document.createRange();
var offset = _.isObject(start) ? start : this.findOffset(start); var offsetList = [];
range.setStart(offset.container, offset.offset); if(_.isNumber(start)) {
if(end && end != start) { offsetList.push(start);
offset = _.isObject(end) ? end : this.findOffset(end); start = offsetList.length - 1;
} }
range.setEnd(offset.container, offset.offset); if(_.isNumber(end)) {
offsetList.push(end);
end = offsetList.length - 1;
}
offsetList = this.findOffsets(offsetList);
var startOffset = _.isObject(start) ? start : offsetList[start];
range.setStart(startOffset.container, startOffset.offsetInContainer);
var endOffset = startOffset;
if(end && end != start) {
endOffset = _.isObject(end) ? end : offsetList[end];
}
range.setEnd(endOffset.container, endOffset.offsetInContainer);
return range; return range;
}; };
var adjustScroll; var adjustScroll;
@ -154,15 +185,20 @@ define([
if(this.cursorY !== coordinates.y) { if(this.cursorY !== coordinates.y) {
this.cursorY = coordinates.y; this.cursorY = coordinates.y;
eventMgr.onCursorCoordinates(coordinates.x, coordinates.y); eventMgr.onCursorCoordinates(coordinates.x, coordinates.y);
if(adjustScroll && settings.cursorFocusRatio) { if(adjustScroll) {
var adjust = inputElt.offsetHeight / 2 * settings.cursorFocusRatio; var adjustTop, adjustBottom;
var cursorMinY = inputElt.scrollTop + adjust; adjustTop = adjustBottom = inputElt.offsetHeight / 2 * settings.cursorFocusRatio;
var cursorMaxY = inputElt.scrollTop + inputElt.offsetHeight - adjust; adjustTop = this.adjustTop || adjustTop;
if(selectionMgr.cursorY < cursorMinY) { adjustBottom = this.adjustBottom || adjustTop;
inputElt.scrollTop += selectionMgr.cursorY - cursorMinY; if(adjustTop && adjustBottom) {
} var cursorMinY = inputElt.scrollTop + adjustTop;
else if(selectionMgr.cursorY > cursorMaxY) { var cursorMaxY = inputElt.scrollTop + inputElt.offsetHeight - adjustBottom;
inputElt.scrollTop += selectionMgr.cursorY - cursorMaxY; if(selectionMgr.cursorY < cursorMinY) {
inputElt.scrollTop += selectionMgr.cursorY - cursorMinY;
}
else if(selectionMgr.cursorY > cursorMaxY) {
inputElt.scrollTop += selectionMgr.cursorY - cursorMaxY;
}
} }
} }
} }
@ -253,11 +289,16 @@ define([
} }
}; };
})(); })();
this.getCoordinates = function(inputOffset, container, offset) { this.getSelectedText = function() {
var min = Math.min(this.selectionStart, this.selectionEnd);
var max = Math.max(this.selectionStart, this.selectionEnd);
return textContent.substring(min, max);
};
this.getCoordinates = function(inputOffset, container, offsetInContainer) {
if(!container) { if(!container) {
offset = this.findOffset(inputOffset); var offset = this.findOffsets([inputOffset])[0];
container = offset.container; container = offset.container;
offset = offset.offset; offsetInContainer = offset.offsetInContainer;
} }
var x = 0; var x = 0;
var y = 0; var y = 0;
@ -268,26 +309,30 @@ define([
var selectedChar = textContent[inputOffset]; var selectedChar = textContent[inputOffset];
var startOffset = { var startOffset = {
container: container, container: container,
offset: offset offsetInContainer: offsetInContainer,
offset: inputOffset
}; };
var endOffset = { var endOffset = {
container: container, container: container,
offset: offset offsetInContainer: offsetInContainer,
offset: inputOffset
}; };
if(inputOffset > 0 && (selectedChar === undefined || selectedChar == '\n')) { if(inputOffset > 0 && (selectedChar === undefined || selectedChar == '\n')) {
if(startOffset.offset === 0) { if(startOffset.offset === 0) {
// Need to calculate offset-1
startOffset = inputOffset - 1; startOffset = inputOffset - 1;
} }
else { else {
startOffset.offset -= 1; startOffset.offsetInContainer -= 1;
} }
} }
else { else {
if(endOffset.offset === container.textContent.length) { if(endOffset.offset === container.textContent.length) {
// Need to calculate offset+1
endOffset = inputOffset + 1; endOffset = inputOffset + 1;
} }
else { else {
endOffset.offset += 1; endOffset.offsetInContainer += 1;
} }
} }
var selectionRange = this.createRange(startOffset, endOffset); var selectionRange = this.createRange(startOffset, endOffset);
@ -351,10 +396,44 @@ define([
range.deleteContents(); range.deleteContents();
range.insertNode(document.createTextNode(replacement)); range.insertNode(document.createTextNode(replacement));
range.detach(); range.detach();
return {
start: startOffset,
end: value.length - endOffset
};
} }
editor.setValue = setValue; editor.setValue = setValue;
function replace(selectionStart, selectionEnd, replacement) {
undoMgr.currentMode = undoMgr.currentMode || 'replace';
var range = selectionMgr.createRange(selectionStart, selectionEnd);
if('' + range == replacement) {
return;
}
range.deleteContents();
range.insertNode(document.createTextNode(replacement));
range.detach();
var endOffset = selectionStart + replacement.length;
selectionMgr.setSelectionStartEnd(endOffset, endOffset);
selectionMgr.updateSelectionRange();
selectionMgr.updateCursorCoordinates(true);
}
editor.replace = replace;
function replaceAll(search, replacement) {
undoMgr.currentMode = undoMgr.currentMode || 'replace';
var value = textContent.replace(search, replacement);
if(value != textContent) {
var offset = editor.setValue(value);
selectionMgr.setSelectionStartEnd(offset.end, offset.end);
selectionMgr.updateSelectionRange();
selectionMgr.updateCursorCoordinates(true);
}
}
editor.replaceAll = replaceAll;
function replacePreviousText(text, replacement) { function replacePreviousText(text, replacement) {
var offset = selectionMgr.selectionStart; var offset = selectionMgr.selectionStart;
if(offset !== selectionMgr.selectionEnd) { if(offset !== selectionMgr.selectionEnd) {
@ -370,7 +449,7 @@ define([
offset = offset - text.length + replacement.length; offset = offset - text.length + replacement.length;
selectionMgr.setSelectionStartEnd(offset, offset); selectionMgr.setSelectionStartEnd(offset, offset);
selectionMgr.updateSelectionRange(); selectionMgr.updateSelectionRange();
selectionMgr.updateCursorCoordinates(); selectionMgr.updateCursorCoordinates(true);
return true; return true;
} }
@ -415,7 +494,11 @@ define([
this.saveState = utils.debounce(function() { this.saveState = utils.debounce(function() {
redoStack = []; redoStack = [];
var currentTime = Date.now(); var currentTime = Date.now();
if(this.currentMode == 'comment' || lastMode == 'newlines' || this.currentMode != lastMode || currentTime - lastTime > 1000) { if(this.currentMode == 'comment' ||
this.currentMode == 'replace' ||
lastMode == 'newlines' ||
this.currentMode != lastMode ||
currentTime - lastTime > 1000) {
undoStack.push(currentState); undoStack.push(currentState);
// Limit the size of the stack // Limit the size of the stack
while(undoStack.length > 100) { while(undoStack.length > 100) {
@ -441,11 +524,12 @@ define([
this.onButtonStateChange(); this.onButtonStateChange();
}, this); }, this);
this.saveSelectionState = _.debounce(function() { this.saveSelectionState = _.debounce(function() {
// Should happen just after saveState
if(this.currentMode === undefined) { if(this.currentMode === undefined) {
selectionStartBefore = selectionMgr.selectionStart; selectionStartBefore = selectionMgr.selectionStart;
selectionEndBefore = selectionMgr.selectionEnd; selectionEndBefore = selectionMgr.selectionEnd;
} }
}, 10); }, 50);
this.canUndo = function() { this.canUndo = function() {
return undoStack.length; return undoStack.length;
}; };
@ -462,7 +546,7 @@ define([
} }
selectionMgr.setSelectionStartEnd(selectionStart, selectionEnd); selectionMgr.setSelectionStartEnd(selectionStart, selectionEnd);
selectionMgr.updateSelectionRange(); selectionMgr.updateSelectionRange();
selectionMgr.updateCursorCoordinates(); selectionMgr.updateCursorCoordinates(true);
var discussionListJSON = fileDesc.discussionListJSON; var discussionListJSON = fileDesc.discussionListJSON;
if(discussionListJSON != state.discussionListJSON) { if(discussionListJSON != state.discussionListJSON) {
var oldDiscussionList = fileDesc.discussionList; var oldDiscussionList = fileDesc.discussionList;
@ -767,6 +851,12 @@ define([
.on('cut', function() { .on('cut', function() {
undoMgr.currentMode = 'cut'; undoMgr.currentMode = 'cut';
adjustCursorPosition(); adjustCursorPosition();
})
.on('focus', function() {
selectionMgr.hasFocus = true;
})
.on('blur', function() {
selectionMgr.hasFocus = false;
}); });
var action = function(action, options) { var action = function(action, options) {

View File

@ -1,307 +1,319 @@
define([ define([
"jquery", "jquery",
"underscore", "underscore",
"crel", "crel",
"utils", "mousetrap",
"logger", "utils",
"classes/Extension", "logger",
"settings", "classes/Extension",
"text!html/settingsExtensionsAccordion.html", "settings",
"extensions/yamlFrontMatterParser", "text!html/settingsExtensionsAccordion.html",
"extensions/markdownSectionParser", "extensions/yamlFrontMatterParser",
"extensions/partialRendering", "extensions/markdownSectionParser",
"extensions/buttonMarkdownSyntax", "extensions/partialRendering",
"extensions/googleAnalytics", "extensions/buttonMarkdownSyntax",
"extensions/twitter", "extensions/googleAnalytics",
"extensions/dialogAbout", "extensions/twitter",
"extensions/dialogManagePublication", "extensions/dialogAbout",
"extensions/dialogManageSynchronization", "extensions/dialogManagePublication",
"extensions/dialogManageSharing", "extensions/dialogManageSynchronization",
"extensions/dialogOpenHarddrive", "extensions/dialogManageSharing",
"extensions/documentTitle", "extensions/dialogOpenHarddrive",
"extensions/documentSelector", "extensions/documentTitle",
"extensions/documentPanel", "extensions/documentSelector",
"extensions/documentManager", "extensions/documentPanel",
"extensions/workingIndicator", "extensions/documentManager",
"extensions/notifications", "extensions/workingIndicator",
"extensions/notifications",
"extensions/umlDiagrams", "extensions/umlDiagrams",
"extensions/markdownExtra", "extensions/markdownExtra",
"extensions/toc", "extensions/toc",
"extensions/mathJax", "extensions/mathJax",
"extensions/emailConverter", "extensions/emailConverter",
"extensions/scrollSync", "extensions/scrollSync",
"extensions/buttonSync", "extensions/buttonSync",
"extensions/buttonPublish", "extensions/buttonPublish",
"extensions/buttonStat", "extensions/buttonStat",
"extensions/buttonHtmlCode", "extensions/buttonHtmlCode",
"extensions/buttonViewer", "extensions/buttonViewer",
"extensions/welcomeTour", "extensions/welcomeTour",
"extensions/shortcuts", "extensions/shortcuts",
"extensions/userCustom", "extensions/userCustom",
"extensions/comments", "extensions/comments",
"extensions/htmlSanitizer", "extensions/findReplace",
"bootstrap", "extensions/htmlSanitizer",
"jquery-waitforimages" "bootstrap",
], function($, _, crel, utils, logger, Extension, settings, settingsExtensionsAccordionHTML) { "jquery-waitforimages"
], function($, _, crel, mousetrap, utils, logger, Extension, settings, settingsExtensionsAccordionHTML) {
var eventMgr = {}; var eventMgr = {};
// Create a list of extensions from module arguments // Create a list of extensions from module arguments
var extensionList = _.chain(arguments).map(function(argument) { var extensionList = _.chain(arguments).map(function(argument) {
return argument instanceof Extension && argument; return argument instanceof Extension && argument;
}).compact().value(); }).compact().value();
// Configure extensions // Configure extensions
var extensionSettings = settings.extensionSettings || {}; var extensionSettings = settings.extensionSettings || {};
_.each(extensionList, function(extension) { _.each(extensionList, function(extension) {
// Set the extension.config attribute from settings or default // Set the extension.config attribute from settings or default
// configuration // configuration
extension.config = _.extend({}, extension.defaultConfig, extensionSettings[extension.extensionId]); extension.config = _.extend({}, extension.defaultConfig, extensionSettings[extension.extensionId]);
if(window.viewerMode === true && extension.disableInViewer === true) { if(window.viewerMode === true && extension.disableInViewer === true) {
// Skip enabling the extension if we are in the viewer and extension // Skip enabling the extension if we are in the viewer and extension
// doesn't support it // doesn't support it
extension.enabled = false; extension.enabled = false;
} }
else { else {
// Enable the extension if it's not optional or it has not been // Enable the extension if it's not optional or it has not been
// disabled by the user // disabled by the user
extension.enabled = !extension.isOptional || extension.config.enabled === undefined || extension.config.enabled === true; extension.enabled = !extension.isOptional || extension.config.enabled === undefined || extension.config.enabled === true;
} }
}); });
// Returns all listeners with the specified name that are implemented in the // Returns all listeners with the specified name that are implemented in the
// enabled extensions // enabled extensions
function getExtensionListenerList(eventName) { function getExtensionListenerList(eventName) {
return _.chain(extensionList).map(function(extension) { return _.chain(extensionList).map(function(extension) {
return extension.enabled && extension[eventName]; return extension.enabled && extension[eventName];
}).compact().value(); }).compact().value();
} }
// Returns a function that calls every listeners with the specified name // Returns a function that calls every listeners with the specified name
// from all enabled extensions // from all enabled extensions
var eventListenerListMap = {}; var eventListenerListMap = {};
function createEventHook(eventName) {
eventListenerListMap[eventName] = getExtensionListenerList(eventName);
return function() {
logger.log(eventName, arguments);
var eventArguments = arguments;
_.each(eventListenerListMap[eventName], function(listener) {
// Use try/catch in case userCustom listener contains error
try {
listener.apply(null, eventArguments);
}
catch(e) {
console.error(_.isObject(e) ? e.stack : e);
}
});
};
}
// Declare an event Hook in the eventMgr that we can fire using eventMgr.eventName() function createEventHook(eventName) {
function addEventHook(eventName) { eventListenerListMap[eventName] = getExtensionListenerList(eventName);
eventMgr[eventName] = createEventHook(eventName); return function() {
} logger.log(eventName, arguments);
var eventArguments = arguments;
_.each(eventListenerListMap[eventName], function(listener) {
// Use try/catch in case userCustom listener contains error
try {
listener.apply(null, eventArguments);
}
catch(e) {
console.error(_.isObject(e) ? e.stack : e);
}
});
};
}
// Used by external modules (not extensions) to listen to events // Declare an event Hook in the eventMgr that we can fire using eventMgr.eventName()
eventMgr.addListener = function(eventName, listener) { function addEventHook(eventName) {
try { eventMgr[eventName] = createEventHook(eventName);
eventListenerListMap[eventName].push(listener); }
}
catch(e) {
console.error('No event listener called ' + eventName);
}
};
// Call every onInit listeners (enabled extensions only) // Used by external modules (not extensions) to listen to events
createEventHook("onInit")(); eventMgr.addListener = function(eventName, listener) {
try {
eventListenerListMap[eventName].push(listener);
}
catch(e) {
console.error('No event listener called ' + eventName);
}
};
// Load/Save extension config from/to settings // Call every onInit listeners (enabled extensions only)
eventMgr.onLoadSettings = function() { createEventHook("onInit")();
logger.log("onLoadSettings");
_.each(extensionList, function(extension) {
var isChecked = !extension.isOptional || extension.config.enabled === undefined || extension.config.enabled === true;
utils.setInputChecked("#input-enable-extension-" + extension.extensionId, isChecked);
// Special case for Markdown Extra and MathJax
if(extension.extensionId == 'markdownExtra') {
utils.setInputChecked("#input-settings-markdown-extra", isChecked);
}
else if(extension.extensionId == 'mathJax') {
utils.setInputChecked("#input-settings-mathjax", isChecked);
}
var onLoadSettingsListener = extension.onLoadSettings;
onLoadSettingsListener && onLoadSettingsListener();
});
};
eventMgr.onSaveSettings = function(newExtensionSettings, event) {
logger.log("onSaveSettings");
_.each(extensionList, function(extension) {
var newExtensionConfig = _.extend({}, extension.defaultConfig);
newExtensionConfig.enabled = utils.getInputChecked("#input-enable-extension-" + extension.extensionId);
var isChecked;
// Special case for Markdown Extra and MathJax
if(extension.extensionId == 'markdownExtra') {
isChecked = utils.getInputChecked("#input-settings-markdown-extra");
if(isChecked != extension.enabled) {
newExtensionConfig.enabled = isChecked;
}
}
else if(extension.extensionId == 'mathJax') {
isChecked = utils.getInputChecked("#input-settings-mathjax");
if(isChecked != extension.enabled) {
newExtensionConfig.enabled = isChecked;
}
}
var onSaveSettingsListener = extension.onSaveSettings;
onSaveSettingsListener && onSaveSettingsListener(newExtensionConfig, event);
newExtensionSettings[extension.extensionId] = newExtensionConfig;
});
};
addEventHook("onMessage"); // Load/Save extension config from/to settings
addEventHook("onError"); eventMgr.onLoadSettings = function() {
addEventHook("onOfflineChanged"); logger.log("onLoadSettings");
addEventHook("onUserActive"); _.each(extensionList, function(extension) {
addEventHook("onAsyncRunning"); var isChecked = !extension.isOptional || extension.config.enabled === undefined || extension.config.enabled === true;
addEventHook("onPeriodicRun"); utils.setInputChecked("#input-enable-extension-" + extension.extensionId, isChecked);
// Special case for Markdown Extra and MathJax
if(extension.extensionId == 'markdownExtra') {
utils.setInputChecked("#input-settings-markdown-extra", isChecked);
}
else if(extension.extensionId == 'mathJax') {
utils.setInputChecked("#input-settings-mathjax", isChecked);
}
var onLoadSettingsListener = extension.onLoadSettings;
onLoadSettingsListener && onLoadSettingsListener();
});
};
eventMgr.onSaveSettings = function(newExtensionSettings, event) {
logger.log("onSaveSettings");
_.each(extensionList, function(extension) {
var newExtensionConfig = _.extend({}, extension.defaultConfig);
newExtensionConfig.enabled = utils.getInputChecked("#input-enable-extension-" + extension.extensionId);
var isChecked;
// Special case for Markdown Extra and MathJax
if(extension.extensionId == 'markdownExtra') {
isChecked = utils.getInputChecked("#input-settings-markdown-extra");
if(isChecked != extension.enabled) {
newExtensionConfig.enabled = isChecked;
}
}
else if(extension.extensionId == 'mathJax') {
isChecked = utils.getInputChecked("#input-settings-mathjax");
if(isChecked != extension.enabled) {
newExtensionConfig.enabled = isChecked;
}
}
var onSaveSettingsListener = extension.onSaveSettings;
onSaveSettingsListener && onSaveSettingsListener(newExtensionConfig, event);
newExtensionSettings[extension.extensionId] = newExtensionConfig;
});
};
// To access modules that are loaded after extensions addEventHook("onMessage");
addEventHook("onEditorCreated"); addEventHook("onError");
addEventHook("onFileMgrCreated"); addEventHook("onOfflineChanged");
addEventHook("onSynchronizerCreated"); addEventHook("onUserActive");
addEventHook("onPublisherCreated"); addEventHook("onAsyncRunning");
addEventHook("onEventMgrCreated"); addEventHook("onPeriodicRun");
// Operations on files // To access modules that are loaded after extensions
addEventHook("onFileCreated"); addEventHook("onEditorCreated");
addEventHook("onFileDeleted"); addEventHook("onFileMgrCreated");
addEventHook("onFileSelected"); addEventHook("onSynchronizerCreated");
addEventHook("onFileOpen"); addEventHook("onPublisherCreated");
addEventHook("onFileClosed"); addEventHook("onEventMgrCreated");
addEventHook("onContentChanged");
addEventHook("onTitleChanged");
// Operations on folders // Operations on files
addEventHook("onFoldersChanged"); addEventHook("onFileCreated");
addEventHook("onFileDeleted");
addEventHook("onFileSelected");
addEventHook("onFileOpen");
addEventHook("onFileClosed");
addEventHook("onContentChanged");
addEventHook("onTitleChanged");
// Sync events // Operations on folders
addEventHook("onSyncRunning"); addEventHook("onFoldersChanged");
addEventHook("onSyncSuccess");
addEventHook("onSyncImportSuccess");
addEventHook("onSyncExportSuccess");
addEventHook("onSyncRemoved");
// Publish events // Sync events
addEventHook("onPublishRunning"); addEventHook("onSyncRunning");
addEventHook("onPublishSuccess"); addEventHook("onSyncSuccess");
addEventHook("onNewPublishSuccess"); addEventHook("onSyncImportSuccess");
addEventHook("onPublishRemoved"); addEventHook("onSyncExportSuccess");
addEventHook("onSyncRemoved");
// Operations on Layout // Publish events
addEventHook("onLayoutCreated"); addEventHook("onPublishRunning");
addEventHook("onLayoutResize"); addEventHook("onPublishSuccess");
addEventHook("onExtensionButtonResize"); addEventHook("onNewPublishSuccess");
addEventHook("onPublishRemoved");
// Operations on editor // Operations on Layout
addEventHook("onPagedownConfigure"); addEventHook("onLayoutCreated");
addEventHook("onSectionsCreated"); addEventHook("onLayoutResize");
addEventHook("onCursorCoordinates"); addEventHook("onExtensionButtonResize");
// Operations on comments // Operations on editor
addEventHook("onDiscussionCreated"); addEventHook("onPagedownConfigure");
addEventHook("onDiscussionRemoved"); addEventHook("onSectionsCreated");
addEventHook("onCommentsChanged"); addEventHook("onCursorCoordinates");
addEventHook("onEditorPopover");
// Refresh twitter buttons // Operations on comments
addEventHook("onTweet"); addEventHook("onDiscussionCreated");
addEventHook("onDiscussionRemoved");
addEventHook("onCommentsChanged");
// Refresh twitter buttons
addEventHook("onTweet");
var onPreviewFinished = createEventHook("onPreviewFinished"); var onPreviewFinished = createEventHook("onPreviewFinished");
var onAsyncPreviewListenerList = getExtensionListenerList("onAsyncPreview"); var onAsyncPreviewListenerList = getExtensionListenerList("onAsyncPreview");
var previewContentsElt; var previewContentsElt;
var $previewContentsElt; var $previewContentsElt;
eventMgr.onAsyncPreview = function() { eventMgr.onAsyncPreview = function() {
logger.log("onAsyncPreview"); logger.log("onAsyncPreview");
function recursiveCall(callbackList) { function recursiveCall(callbackList) {
var callback = callbackList.length ? callbackList.shift() : function() { var callback = callbackList.length ? callbackList.shift() : function() {
setTimeout(function() { setTimeout(function() {
var html = ""; var html = "";
_.each(previewContentsElt.children, function(elt) { _.each(previewContentsElt.children, function(elt) {
html += elt.innerHTML; html += elt.innerHTML;
}); });
var htmlWithComments = utils.trim(html); var htmlWithComments = utils.trim(html);
var htmlWithoutComments = htmlWithComments.replace(/ <span class="comment label label-danger">.*?<\/span> /g, ''); var htmlWithoutComments = htmlWithComments.replace(/ <span class="comment label label-danger">.*?<\/span> /g, '');
onPreviewFinished(htmlWithComments, htmlWithoutComments); onPreviewFinished(htmlWithComments, htmlWithoutComments);
}, 10); }, 10);
}; };
callback(function() { callback(function() {
recursiveCall(callbackList); recursiveCall(callbackList);
}); });
} }
recursiveCall(onAsyncPreviewListenerList.concat([function(callback) {
// We assume some images are loading asynchronously after the preview
$previewContentsElt.waitForImages(callback);
}]));
};
var onReady = createEventHook("onReady"); recursiveCall(onAsyncPreviewListenerList.concat([
eventMgr.onReady = function() { function(callback) {
previewContentsElt = document.getElementById('preview-contents'); // We assume some images are loading asynchronously after the preview
$previewContentsElt = $(previewContentsElt); $previewContentsElt.waitForImages(callback);
}
]));
};
// Create a button from an extension listener var onReady = createEventHook("onReady");
var createBtn = function(listener) { eventMgr.onReady = function() {
var buttonGrpElt = crel('div', { previewContentsElt = document.getElementById('preview-contents');
class: 'btn-group' $previewContentsElt = $(previewContentsElt);
});
var btnElt = listener();
if(_.isString(btnElt)) {
buttonGrpElt.innerHTML = btnElt;
}
else if(_.isElement(btnElt)) {
buttonGrpElt.appendChild(btnElt);
}
return buttonGrpElt;
};
if(window.viewerMode === false) { // Create a button from an extension listener
// Create accordion in settings dialog var createBtn = function(listener) {
var accordionHtml = _.chain(extensionList).sortBy(function(extension) { var buttonGrpElt = crel('div', {
return extension.extensionName.toLowerCase(); class: 'btn-group'
}).reduce(function(html, extension) { });
return html + (extension.settingsBlock ? _.template(settingsExtensionsAccordionHTML, { var btnElt = listener();
extensionId: extension.extensionId, if(_.isString(btnElt)) {
extensionName: extension.extensionName, buttonGrpElt.innerHTML = btnElt;
isOptional: extension.isOptional, }
settingsBlock: extension.settingsBlock else if(_.isElement(btnElt)) {
}) : ""); buttonGrpElt.appendChild(btnElt);
}, "").value(); }
document.querySelector('.accordion-extensions').innerHTML = accordionHtml; return buttonGrpElt;
};
// Create extension buttons if(window.viewerMode === false) {
logger.log("onCreateButton"); // Create accordion in settings dialog
var onCreateButtonListenerList = getExtensionListenerList("onCreateButton"); var accordionHtml = _.chain(extensionList).sortBy(function(extension) {
var extensionButtonsFragment = document.createDocumentFragment(); return extension.extensionName.toLowerCase();
_.each(onCreateButtonListenerList, function(listener) { }).reduce(function(html, extension) {
extensionButtonsFragment.appendChild(createBtn(listener)); return html + (extension.settingsBlock ? _.template(settingsExtensionsAccordionHTML, {
}); extensionId: extension.extensionId,
document.querySelector('.extension-buttons').appendChild(extensionButtonsFragment); extensionName: extension.extensionName,
} isOptional: extension.isOptional,
settingsBlock: extension.settingsBlock
}) : "");
}, "").value();
document.querySelector('.accordion-extensions').innerHTML = accordionHtml;
// Create extension preview buttons // Create extension buttons
logger.log("onCreatePreviewButton"); logger.log("onCreateButton");
var onCreatePreviewButtonListenerList = getExtensionListenerList("onCreatePreviewButton"); var onCreateButtonListenerList = getExtensionListenerList("onCreateButton");
var extensionPreviewButtonsFragment = document.createDocumentFragment(); var extensionButtonsFragment = document.createDocumentFragment();
_.each(onCreatePreviewButtonListenerList, function(listener) { _.each(onCreateButtonListenerList, function(listener) {
extensionPreviewButtonsFragment.appendChild(createBtn(listener)); extensionButtonsFragment.appendChild(createBtn(listener));
}); });
var previewButtonsElt = document.querySelector('.extension-preview-buttons'); document.querySelector('.extension-buttons').appendChild(extensionButtonsFragment);
previewButtonsElt.appendChild(extensionPreviewButtonsFragment); }
// Call onReady listeners // Create extension preview buttons
onReady(); logger.log("onCreatePreviewButton");
}; var onCreatePreviewButtonListenerList = getExtensionListenerList("onCreatePreviewButton");
var extensionPreviewButtonsFragment = document.createDocumentFragment();
_.each(onCreatePreviewButtonListenerList, function(listener) {
extensionPreviewButtonsFragment.appendChild(createBtn(listener));
});
var previewButtonsElt = document.querySelector('.extension-preview-buttons');
previewButtonsElt.appendChild(extensionPreviewButtonsFragment);
// For extensions that need to call other extensions // Shall close every popover
eventMgr.onEventMgrCreated(eventMgr); mousetrap.bind('escape', function() {
return eventMgr; eventMgr.onEditorPopover();
});
// Call onReady listeners
onReady();
};
// For extensions that need to call other extensions
eventMgr.onEventMgrCreated(eventMgr);
return eventMgr;
}); });

View File

@ -226,6 +226,12 @@ define([
currentContext && currentContext.$commentElt.popover('toggle').popover('destroy'); currentContext && currentContext.$commentElt.popover('toggle').popover('destroy');
} }
comments.onEditorPopover = function() {
closeCurrentPopover();
editor.focus();
editor.adjustCursorPosition();
};
comments.onDiscussionCreated = function(fileDesc) { comments.onDiscussionCreated = function(fileDesc) {
currentFileDesc === fileDesc && refreshDiscussions(); currentFileDesc === fileDesc && refreshDiscussions();
}; };
@ -265,7 +271,7 @@ define([
} }
comments.onReady = function() { comments.onReady = function() {
cssApplier = rangy.createCssClassApplier("comment-highlight", { cssApplier = rangy.createCssClassApplier('comment-highlight', {
normalize: false normalize: false
}); });
var previousContent = ''; var previousContent = '';
@ -308,7 +314,7 @@ define([
selector: '#wmd-input > .editor-margin > .discussion' selector: '#wmd-input > .editor-margin > .discussion'
}); });
$(marginElt).on('show.bs.popover', function(evt) { $(marginElt).on('show.bs.popover', function(evt) {
closeCurrentPopover(); eventMgr.onEditorPopover();
var context = new Context(evt.target, currentFileDesc); var context = new Context(evt.target, currentFileDesc);
currentContext = context; currentContext = context;
@ -348,18 +354,10 @@ define([
var $addButton = $(popoverElt.querySelector('.action-add-comment')); var $addButton = $(popoverElt.querySelector('.action-add-comment'));
$().add(context.$contentInputElt).add(context.$authorInputElt).keydown(function(evt) { $().add(context.$contentInputElt).add(context.$authorInputElt).keydown(function(evt) {
// Enter key if(evt.which === 13) {
switch(evt.which) { // Enter key
case 13: evt.preventDefault();
evt.preventDefault(); $addButton.click();
$addButton.click();
return;
case 27:
evt.preventDefault();
closeCurrentPopover();
editor.focus();
editor.adjustCursorPosition();
return;
} }
}); });
$addButton.click(function(evt) { $addButton.click(function(evt) {
@ -418,7 +416,7 @@ define([
context.rangyRange = rangy.createRange(); context.rangyRange = rangy.createRange();
context.rangyRange.setStart(context.selectionRange.startContainer, context.selectionRange.startOffset); context.rangyRange.setStart(context.selectionRange.startContainer, context.selectionRange.startOffset);
context.rangyRange.setEnd(context.selectionRange.endContainer, context.selectionRange.endOffset); context.rangyRange.setEnd(context.selectionRange.endContainer, context.selectionRange.endOffset);
setTimeout(function() { // Need to delay this because it's not refreshed properly setTimeout(function() { // Delay this because not refreshed properly
if(currentContext === context) { if(currentContext === context) {
cssApplier.applyToRange(context.rangyRange); cssApplier.applyToRange(context.rangyRange);
} }
@ -452,7 +450,6 @@ define([
evt.stopPropagation(); evt.stopPropagation();
}); });
var $newCommentElt = $(newCommentElt); var $newCommentElt = $(newCommentElt);
$openDiscussionElt = $('.button-open-discussion').click(function(evt) { $openDiscussionElt = $('.button-open-discussion').click(function(evt) {
var $commentElt = $newCommentElt; var $commentElt = $newCommentElt;

View File

@ -0,0 +1,267 @@
define([
"jquery",
"underscore",
"crel",
"utils",
"classes/Extension",
"mousetrap",
"rangy",
"text!html/findReplace.html",
"text!html/findReplaceSettingsBlock.html"
], function($, _, crel, utils, Extension, mousetrap, rangy, findReplaceHTML, findReplaceSettingsBlockHTML) {
var findReplace = new Extension("findReplace", 'Find and Replace', true, true);
findReplace.settingsBlock = findReplaceSettingsBlockHTML;
findReplace.defaultConfig = {
findReplaceShortcut: 'mod+f'
};
findReplace.onLoadSettings = function() {
utils.setInputValue("#input-find-replace-shortcut", findReplace.config.findReplaceShortcut);
};
findReplace.onSaveSettings = function(newConfig, event) {
newConfig.findReplaceShortcut = utils.getInputTextValue("#input-find-replace-shortcut", event);
};
var editor;
findReplace.onEditorCreated = function(editorParam) {
editor = editorParam;
};
var eventMgr;
findReplace.onEventMgrCreated = function(eventMgrParam) {
eventMgr = eventMgrParam;
};
var rangeList = [];
var offsetList = [];
var highlightCssApplier, selectCssApplier;
var selectRange;
function resetHighlight() {
resetSelect();
rangeList.forEach(function(rangyRange) {
try {
highlightCssApplier.undoToRange(rangyRange);
}
catch(e) {
}
rangyRange.detach();
});
rangeList = [];
}
function resetSelect() {
if(selectRange) {
try {
selectRange && selectCssApplier.undoToRange(selectRange);
}
catch(e) {}
selectRange.toBeDetached && selectRange.detach();
selectRange = undefined;
}
}
var contentElt;
var $findReplaceElt, $searchForInputElt, $replaceWithInputElt;
var foundCounterElt, $caseSensitiveElt, $regexpElt;
var previousText = '';
var previousCaseSensitive = false;
var previousUseRegexp = false;
var shown = false;
var regex;
function highlight(force) {
if(!shown) {
return;
}
var text = $searchForInputElt.val();
var caseSensitive = $caseSensitiveElt.prop('checked');
var useRegexp = $regexpElt.prop('checked');
if(!force && text == previousText && caseSensitive == previousCaseSensitive && useRegexp == previousUseRegexp) {
return;
}
previousText = text;
previousCaseSensitive = caseSensitive;
previousUseRegexp = useRegexp;
resetHighlight();
var lastOffset = {};
var lastRange;
function adjustOffset(offset) {
if(offset.container === lastOffset.container) {
// adjust the offset after rangy has modified the text node
return {
container: lastRange.endContainer.parentElement.nextSibling,
offsetInContainer: offset.offsetInContainer - lastOffset.offsetInContainer,
offset: offset.offset
};
}
return offset;
}
offsetList = [];
var found = 0;
var textLength = text.length;
if(textLength) {
try {
var flags = caseSensitive ? 'g' : 'gi';
text = useRegexp ? text : text.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&");
regex = new RegExp(text, flags);
editor.getValue().replace(regex, function(match, offset) {
offsetList.push({
start: offset,
end: offset + match.length
});
});
found = offsetList.length;
// Highly CPU consuming, so add a limit
if(offsetList.length < 200) {
var rangeOffsets = [];
offsetList.forEach(function(offset) {
rangeOffsets.push(offset.start);
rangeOffsets.push(offset.end);
});
rangeOffsets = editor.selectionMgr.findOffsets(rangeOffsets);
for(var i = 0; i < rangeOffsets.length; i += 2) {
var offsetStart = rangeOffsets[i];
var offsetEnd = rangeOffsets[i + 1];
var adjustedOffsetStart = adjustOffset(offsetStart);
var adjustedOffsetEnd = adjustOffset(offsetEnd);
var rangyRange = rangy.createRange();
rangyRange.setStart(adjustedOffsetStart.container, adjustedOffsetStart.offsetInContainer);
rangyRange.setEnd(adjustedOffsetEnd.container, adjustedOffsetEnd.offsetInContainer);
lastOffset = offsetEnd;
lastRange = rangyRange;
highlightCssApplier.applyToRange(rangyRange);
rangeList[offsetStart.offset] = rangyRange;
}
editor.selectionMgr.hasFocus && editor.selectionMgr.updateSelectionRange();
}
}
catch(e) {
}
}
foundCounterElt.innerHTML = found;
}
function show() {
eventMgr.onEditorPopover();
shown = true;
$findReplaceElt.show();
$searchForInputElt.focus();
editor.selectionMgr.adjustTop = 50;
editor.selectionMgr.adjustBottom = 220;
highlight(true);
}
function hide() {
shown = false;
$findReplaceElt.hide();
resetHighlight();
editor.selectionMgr.adjustTop = 0;
editor.selectionMgr.adjustBottom = 0;
editor.focus();
}
findReplace.onEditorPopover = function() {
hide();
};
function find() {
resetSelect();
var position = Math.min(editor.selectionMgr.selectionStart, editor.selectionMgr.selectionEnd);
var offset = _.find(offsetList, function(offset) {
return offset.start > position;
});
if(!offset) {
offset = _.first(offsetList);
}
if(!offset) {
return;
}
selectRange = rangeList[offset.start];
if(!selectRange) {
var range = editor.selectionMgr.createRange(offset.start, offset.end);
selectRange = rangy.createRange();
selectRange.setStart(range.startContainer, range.startOffset);
selectRange.setEnd(range.endContainer, range.endOffset);
selectRange.toBeDetached = true;
}
selectCssApplier.applyToRange(selectRange);
selectRange.start = offset.start;
selectRange.end = offset.end;
editor.selectionMgr.setSelectionStartEnd(offset.start, offset.end);
editor.selectionMgr.updateCursorCoordinates(true);
}
function replace() {
if(!selectRange) {
return find();
}
var replacement = $replaceWithInputElt.val();
editor.replace(selectRange.start, selectRange.end, replacement);
setTimeout(function() {
find();
$replaceWithInputElt.focus();
}, 1);
}
function replaceAll() {
var replacement = $replaceWithInputElt.val();
editor.replaceAll(regex, replacement);
}
findReplace.onContentChanged = _.bind(highlight, null, true);
findReplace.onFileOpen = _.bind(highlight, null, true);
findReplace.onReady = function() {
highlightCssApplier = rangy.createCssClassApplier('find-replace-highlight', {
normalize: false
});
selectCssApplier = rangy.createCssClassApplier('find-replace-select', {
normalize: false
});
contentElt = document.querySelector('#wmd-input .editor-content');
var elt = crel('div', {
class: 'find-replace'
});
$findReplaceElt = $(elt).hide();
elt.innerHTML = findReplaceHTML;
document.querySelector('.layout-wrapper-l2').appendChild(elt);
$('.button-find-replace-dismiss').click(function() {
hide();
});
foundCounterElt = elt.querySelector('.found-counter');
$caseSensitiveElt = $findReplaceElt.find('.checkbox-case-sensitive').change(_.bind(highlight, null, false));
$regexpElt = $findReplaceElt.find('.checkbox-regexp').change(_.bind(highlight, null, false));
$findReplaceElt.find('.search-button').click(find);
$searchForInputElt = $('#input-find-replace-search-for').keyup(_.bind(highlight, null, false));
$findReplaceElt.find('.replace-button').click(replace);
$replaceWithInputElt = $('#input-find-replace-replace-with');
$findReplaceElt.find('.replace-all-button').click(replaceAll);
// Key bindings
$().add($searchForInputElt).add($replaceWithInputElt).keydown(function(evt) {
if(evt.which === 13) {
// Enter key
evt.preventDefault();
find();
}
});
mousetrap.bind(findReplace.config.findReplaceShortcut, function(e) {
var newSearch = editor.selectionMgr.getSelectedText();
if(newSearch) {
$searchForInputElt.val(newSearch);
}
show();
e.preventDefault();
});
};
return findReplace;
});

View File

@ -1,4 +1,4 @@
<button class="btn btn-info dropdown-toggle action-html-code" title="HTML code" data-toggle="dropdown"> <button class="btn btn-success dropdown-toggle action-html-code" title="HTML code" data-toggle="dropdown">
<i class="icon-code"></i> <i class="icon-code"></i>
</button> </button>
<div class="dropdown-menu pull-right"> <div class="dropdown-menu pull-right">

View File

@ -1,4 +1,4 @@
<button class="btn btn-info dropdown-toggle button-markdown-syntax" title="Markdown syntax" data-toggle="dropdown"> <button class="btn btn-success dropdown-toggle button-markdown-syntax" title="Markdown syntax" data-toggle="dropdown">
<i class="icon-help-circled"></i> <i class="icon-help-circled"></i>
</button> </button>
<div class="dropdown-menu pull-right"> <div class="dropdown-menu pull-right">

View File

@ -1,4 +1,4 @@
<button class="btn btn-info dropdown-toggle stat-button" title="Document statistics" data-toggle="dropdown"> <button class="btn btn-success dropdown-toggle stat-button" title="Document statistics" data-toggle="dropdown">
<i class="icon-chart-bar"></i> <i class="icon-chart-bar"></i>
<span class="value"></span> <span class="value"></span>
</button> </button>

View File

@ -1,4 +1,4 @@
<button class="btn btn-info dropdown-toggle" title="Table of contents" data-toggle="dropdown"> <button class="btn btn-success dropdown-toggle" title="Table of contents" data-toggle="dropdown">
<i class="icon-list"></i> <i class="icon-list"></i>
</button> </button>
<div class="dropdown-menu pull-right"> <div class="dropdown-menu pull-right">

View File

@ -1,4 +1,4 @@
<a href="viewer" class="btn btn-info dropdown-toggle" <a href="viewer" class="btn btn-success dropdown-toggle"
title="Open in viewer"> title="Open in viewer">
<i class="icon-resize-full"></i> <i class="icon-resize-full"></i>
</a> </a>

View File

@ -0,0 +1,33 @@
<button type="button" class="close button-find-replace-dismiss">×</button>
<div class="form-inline">
<div class="form-group">
<label for="input-find-replace-search-for">Search for</label>
<input class="form-control" id="input-find-replace-search-for" placeholder="Search for">
</div>
<div class="form-group">
<label for="input-find-replace-replace-with">Replace with</label>
<input class="form-control" id="input-find-replace-replace-with" placeholder="Replace with">
</div>
</div>
<div class="pull-right">
<div class="help-block text-right">
<span class="found-counter">0</span> found
</div>
<div>
<button type="button" class="btn btn-primary search-button">Search</button>
<button type="button" class="btn btn-default replace-button">Replace</button>
<button type="button" class="btn btn-default replace-all-button">All</button>
</div>
</div>
<div class="pull-left">
<div class="checkbox">
<label>
<input type="checkbox" class="checkbox-case-sensitive"> Case sensitive
</label>
</div>
<div class="checkbox">
<label>
<input type="checkbox" class="checkbox-regexp"> Regular expression
</label>
</div>
</div>

View File

@ -0,0 +1,11 @@
<p>Helps to find and replace text in the current document.</p>
<div class="form-horizontal">
<div class="form-group">
<label class="col-sm-5 control-label"
for="input-find-replace-shortcut">Shortcut <a href="http://craig.is/killing/mice#keys" target="_blank">(?)</a></label>
<div class="col-sm-6">
<input type="text" id="input-find-replace-shortcut"
class="form-control">
</div>
</div>
</div>

View File

@ -105,10 +105,10 @@
@btn-primary-bg: @primary-bg; @btn-primary-bg: @primary-bg;
@btn-primary-border: fade(@secondary, 5%); @btn-primary-border: fade(@secondary, 5%);
@btn-primary-hover-bg: mix(@primary-desaturated, @btn-primary-bg, 7.5%); @btn-primary-hover-bg: mix(@primary-desaturated, @btn-primary-bg, 7.5%);
@btn-success-color: darken(@primary-desaturated, 25%); @btn-success-color: darken(@primary-desaturated, 20%);
@btn-success-bg: @transparent; @btn-success-bg: @transparent;
@btn-success-border: @transparent; @btn-success-border: @transparent;
@btn-success-hover-bg: fade(@primary-desaturated, 7.5%); @btn-success-hover-bg: fade(@primary-desaturated, 5%);
@btn-info-color: fade(@secondary-desaturated, 35%); @btn-info-color: fade(@secondary-desaturated, 35%);
@btn-info-bg: @transparent; @btn-info-bg: @transparent;
@btn-info-border: @transparent; @btn-info-border: @transparent;
@ -130,10 +130,10 @@
@popover-arrow-outer-color: @secondary-border-color; @popover-arrow-outer-color: @secondary-border-color;
@popover-title-bg: @transparent; @popover-title-bg: @transparent;
@alert-border-radius: 0; @alert-border-radius: 0;
@label-warning-bg: spin(darken(@logo-yellow, 4%), -10); @label-warning-bg: spin(darken(@logo-yellow, 4%), -5);
@state-warning-text: spin(darken(@logo-yellow, 14%), -10); @state-warning-text: spin(darken(@logo-yellow, 14%), -5);
@state-warning-bg: fade(spin(@logo-yellow, -10), 12%); @state-warning-bg: fade(spin(@logo-yellow, -5), 12%);
@state-warning-border: fade(spin(@logo-yellow, -10), 24%); @state-warning-border: fade(spin(@logo-yellow, -5), 24%);
@label-danger-bg: spin(darken(@logo-orange, 4%), -8); @label-danger-bg: spin(darken(@logo-orange, 4%), -8);
@state-danger-text: spin(darken(@logo-orange, 18%), -8); @state-danger-text: spin(darken(@logo-orange, 18%), -8);
@state-danger-bg: fade(spin(@logo-orange, -8), 10%); @state-danger-bg: fade(spin(@logo-orange, -8), 10%);
@ -162,7 +162,7 @@ body {
.user-select(none); .user-select(none);
} }
.dropdown-menu, .modal-content, .panel-content, .search-bar, .popover { .dropdown-menu, .modal-content, .panel-content, .search-bar, .popover, .find-replace {
.box-shadow(0 4px 16px rgba(0,0,0,.225)); .box-shadow(0 4px 16px rgba(0,0,0,.225));
} }
@ -699,7 +699,6 @@ a {
padding: 15px 20px; padding: 15px 20px;
z-index: 3; z-index: 3;
border: 1px solid @secondary-border-color; border: 1px solid @secondary-border-color;
border-top: 0;
border-radius: 6px; border-radius: 6px;
.nav { .nav {
margin-bottom: 10px; margin-bottom: 10px;
@ -736,7 +735,7 @@ a {
// Dropdown document selector // Dropdown document selector
.dropdown-file-selector { .dropdown-file-selector {
top: 6px; top: 6px;
right: 30px; right: 55px;
left: auto; left: auto;
margin: 0; margin: 0;
min-width: 280px; min-width: 280px;
@ -830,8 +829,8 @@ a {
right: 0; right: 0;
bottom: 0; bottom: 0;
z-index: 40; z-index: 40;
background-color: @btn-info-hover-bg; background-color: @navbar-default-bg;
border: 1px solid @btn-info-hover-border; border: 1px solid @navbar-default-border;
border-radius: @border-radius-base; border-radius: @border-radius-base;
cursor: move; cursor: move;
@ -880,10 +879,7 @@ a {
} }
.drag-me { .drag-me {
color: @btn-info-color; color: @btn-success-color;
&.info-tooltip {
color: @btn-info-hover-color;
}
i:before { i:before {
width: 5px; width: 5px;
} }
@ -1124,9 +1120,9 @@ a {
} }
} }
&.added { &.added {
color: fade(@label-warning-bg, 50%); color: fade(@label-warning-bg, 70%);
&:hover, &.active, &.active:hover { &:hover, &.active, &.active:hover {
color: fade(@label-warning-bg, 80%) !important; color: fade(@label-warning-bg, 100%) !important;
} }
} }
&.replied { &.replied {
@ -1159,6 +1155,14 @@ a {
background-color: fade(@label-warning-bg, 30%); background-color: fade(@label-warning-bg, 30%);
} }
.find-replace-highlight {
background-color: fade(@logo-yellow, 60%);
}
.find-replace-select {
background-color: rgb(181, 213, 255);
}
.conflict { .conflict {
font-weight: bold; font-weight: bold;
color: @label-danger-bg; color: @label-danger-bg;
@ -1282,6 +1286,26 @@ a {
} }
} }
.find-replace {
position: absolute;
bottom: 0;
width: 410px;
background-color: @secondary-bg;
padding: 15px 20px;
border-top: 1px solid @secondary-border-color;
border-right: 1px solid @secondary-border-color;
border-top-right-radius: 6px;
.form-group {
width: 180px;
padding: 0 5px;
}
.close {
position: absolute;
right: 20px;
top: 10px;
}
}
/***************************** /*****************************
* Preview * Preview