Implemented find and replace extension
This commit is contained in:
parent
e01db60b07
commit
82be3a44f2
@ -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) {
|
||||||
|
@ -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();
|
||||||
};
|
};
|
||||||
|
@ -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;
|
||||||
|
267
public/res/extensions/findReplace.js
Normal file
267
public/res/extensions/findReplace.js
Normal 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;
|
||||||
|
});
|
@ -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">
|
||||||
|
@ -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">
|
||||||
|
@ -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>
|
||||||
|
@ -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">
|
||||||
|
@ -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>
|
||||||
|
33
public/res/html/findReplace.html
Normal file
33
public/res/html/findReplace.html
Normal 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>
|
11
public/res/html/findReplaceSettingsBlock.html
Normal file
11
public/res/html/findReplaceSettingsBlock.html
Normal 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>
|
@ -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
|
||||||
|
Loading…
Reference in New Issue
Block a user