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 -= text.length; offset = offsetList.shift();
} }
return { walkerOffset = newWalkerOffset;
}
do {
result.push({
container: walker.currentNode, container: walker.currentNode,
offset: text.length 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,10 +185,14 @@ 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;
adjustBottom = this.adjustBottom || adjustTop;
if(adjustTop && adjustBottom) {
var cursorMinY = inputElt.scrollTop + adjustTop;
var cursorMaxY = inputElt.scrollTop + inputElt.offsetHeight - adjustBottom;
if(selectionMgr.cursorY < cursorMinY) { if(selectionMgr.cursorY < cursorMinY) {
inputElt.scrollTop += selectionMgr.cursorY - cursorMinY; inputElt.scrollTop += selectionMgr.cursorY - cursorMinY;
} }
@ -166,6 +201,7 @@ define([
} }
} }
} }
}
adjustScroll = false; adjustScroll = false;
}, this); }, this);
this.updateCursorCoordinates = function(adjustScrollParam) { this.updateCursorCoordinates = function(adjustScrollParam) {
@ -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

@ -2,6 +2,7 @@ define([
"jquery", "jquery",
"underscore", "underscore",
"crel", "crel",
"mousetrap",
"utils", "utils",
"logger", "logger",
"classes/Extension", "classes/Extension",
@ -39,10 +40,11 @@ define([
"extensions/shortcuts", "extensions/shortcuts",
"extensions/userCustom", "extensions/userCustom",
"extensions/comments", "extensions/comments",
"extensions/findReplace",
"extensions/htmlSanitizer", "extensions/htmlSanitizer",
"bootstrap", "bootstrap",
"jquery-waitforimages" "jquery-waitforimages"
], function($, _, crel, utils, logger, Extension, settings, settingsExtensionsAccordionHTML) { ], function($, _, crel, mousetrap, utils, logger, Extension, settings, settingsExtensionsAccordionHTML) {
var eventMgr = {}; var eventMgr = {};
@ -80,6 +82,7 @@ define([
// 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) { function createEventHook(eventName) {
eventListenerListMap[eventName] = getExtensionListenerList(eventName); eventListenerListMap[eventName] = getExtensionListenerList(eventName);
return function() { return function() {
@ -205,6 +208,7 @@ define([
addEventHook("onPagedownConfigure"); addEventHook("onPagedownConfigure");
addEventHook("onSectionsCreated"); addEventHook("onSectionsCreated");
addEventHook("onCursorCoordinates"); addEventHook("onCursorCoordinates");
addEventHook("onEditorPopover");
// Operations on comments // Operations on comments
addEventHook("onDiscussionCreated"); addEventHook("onDiscussionCreated");
@ -237,10 +241,13 @@ define([
recursiveCall(callbackList); recursiveCall(callbackList);
}); });
} }
recursiveCall(onAsyncPreviewListenerList.concat([function(callback) {
recursiveCall(onAsyncPreviewListenerList.concat([
function(callback) {
// We assume some images are loading asynchronously after the preview // We assume some images are loading asynchronously after the preview
$previewContentsElt.waitForImages(callback); $previewContentsElt.waitForImages(callback);
}])); }
]));
}; };
var onReady = createEventHook("onReady"); var onReady = createEventHook("onReady");
@ -297,6 +304,11 @@ define([
var previewButtonsElt = document.querySelector('.extension-preview-buttons'); var previewButtonsElt = document.querySelector('.extension-preview-buttons');
previewButtonsElt.appendChild(extensionPreviewButtonsFragment); previewButtonsElt.appendChild(extensionPreviewButtonsFragment);
// Shall close every popover
mousetrap.bind('escape', function() {
eventMgr.onEditorPopover();
});
// Call onReady listeners // Call onReady listeners
onReady(); onReady();
}; };

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) {
if(evt.which === 13) {
// Enter key // Enter key
switch(evt.which) {
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