Implemented find and replace extension
This commit is contained in:
parent
e01db60b07
commit
82be3a44f2
@ -117,34 +117,65 @@ define([
|
||||
this.selectionStart = 0;
|
||||
this.selectionEnd = 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 text = '';
|
||||
var walkerOffset = 0;
|
||||
while(walker.nextNode()) {
|
||||
text = walker.currentNode.nodeValue || '';
|
||||
if(text.length > offset) {
|
||||
return {
|
||||
var newWalkerOffset = walkerOffset + text.length;
|
||||
while(newWalkerOffset > offset) {
|
||||
result.push({
|
||||
container: walker.currentNode,
|
||||
offsetInContainer: offset - walkerOffset,
|
||||
offset: offset
|
||||
};
|
||||
});
|
||||
if(!offsetList.length) {
|
||||
return result;
|
||||
}
|
||||
offset = offsetList.shift();
|
||||
}
|
||||
offset -= text.length;
|
||||
walkerOffset = newWalkerOffset;
|
||||
}
|
||||
return {
|
||||
container: walker.currentNode,
|
||||
offset: text.length
|
||||
};
|
||||
do {
|
||||
result.push({
|
||||
container: walker.currentNode,
|
||||
offsetInContainer: walkerOffset,
|
||||
offset: offset
|
||||
});
|
||||
offset = offsetList.shift();
|
||||
}
|
||||
while(offset);
|
||||
return result;
|
||||
};
|
||||
this.createRange = function(start, end) {
|
||||
start = start < 0 ? 0 : start;
|
||||
end = end < 0 ? 0 : end;
|
||||
var range = document.createRange();
|
||||
var offset = _.isObject(start) ? start : this.findOffset(start);
|
||||
range.setStart(offset.container, offset.offset);
|
||||
if(end && end != start) {
|
||||
offset = _.isObject(end) ? end : this.findOffset(end);
|
||||
var offsetList = [];
|
||||
if(_.isNumber(start)) {
|
||||
offsetList.push(start);
|
||||
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;
|
||||
};
|
||||
var adjustScroll;
|
||||
@ -154,15 +185,20 @@ define([
|
||||
if(this.cursorY !== coordinates.y) {
|
||||
this.cursorY = coordinates.y;
|
||||
eventMgr.onCursorCoordinates(coordinates.x, coordinates.y);
|
||||
if(adjustScroll && settings.cursorFocusRatio) {
|
||||
var adjust = inputElt.offsetHeight / 2 * settings.cursorFocusRatio;
|
||||
var cursorMinY = inputElt.scrollTop + adjust;
|
||||
var cursorMaxY = inputElt.scrollTop + inputElt.offsetHeight - adjust;
|
||||
if(selectionMgr.cursorY < cursorMinY) {
|
||||
inputElt.scrollTop += selectionMgr.cursorY - cursorMinY;
|
||||
}
|
||||
else if(selectionMgr.cursorY > cursorMaxY) {
|
||||
inputElt.scrollTop += selectionMgr.cursorY - cursorMaxY;
|
||||
if(adjustScroll) {
|
||||
var adjustTop, adjustBottom;
|
||||
adjustTop = adjustBottom = inputElt.offsetHeight / 2 * settings.cursorFocusRatio;
|
||||
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) {
|
||||
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) {
|
||||
offset = this.findOffset(inputOffset);
|
||||
var offset = this.findOffsets([inputOffset])[0];
|
||||
container = offset.container;
|
||||
offset = offset.offset;
|
||||
offsetInContainer = offset.offsetInContainer;
|
||||
}
|
||||
var x = 0;
|
||||
var y = 0;
|
||||
@ -268,26 +309,30 @@ define([
|
||||
var selectedChar = textContent[inputOffset];
|
||||
var startOffset = {
|
||||
container: container,
|
||||
offset: offset
|
||||
offsetInContainer: offsetInContainer,
|
||||
offset: inputOffset
|
||||
};
|
||||
var endOffset = {
|
||||
container: container,
|
||||
offset: offset
|
||||
offsetInContainer: offsetInContainer,
|
||||
offset: inputOffset
|
||||
};
|
||||
if(inputOffset > 0 && (selectedChar === undefined || selectedChar == '\n')) {
|
||||
if(startOffset.offset === 0) {
|
||||
// Need to calculate offset-1
|
||||
startOffset = inputOffset - 1;
|
||||
}
|
||||
else {
|
||||
startOffset.offset -= 1;
|
||||
startOffset.offsetInContainer -= 1;
|
||||
}
|
||||
}
|
||||
else {
|
||||
if(endOffset.offset === container.textContent.length) {
|
||||
// Need to calculate offset+1
|
||||
endOffset = inputOffset + 1;
|
||||
}
|
||||
else {
|
||||
endOffset.offset += 1;
|
||||
endOffset.offsetInContainer += 1;
|
||||
}
|
||||
}
|
||||
var selectionRange = this.createRange(startOffset, endOffset);
|
||||
@ -351,10 +396,44 @@ define([
|
||||
range.deleteContents();
|
||||
range.insertNode(document.createTextNode(replacement));
|
||||
range.detach();
|
||||
return {
|
||||
start: startOffset,
|
||||
end: value.length - endOffset
|
||||
};
|
||||
}
|
||||
|
||||
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) {
|
||||
var offset = selectionMgr.selectionStart;
|
||||
if(offset !== selectionMgr.selectionEnd) {
|
||||
@ -370,7 +449,7 @@ define([
|
||||
offset = offset - text.length + replacement.length;
|
||||
selectionMgr.setSelectionStartEnd(offset, offset);
|
||||
selectionMgr.updateSelectionRange();
|
||||
selectionMgr.updateCursorCoordinates();
|
||||
selectionMgr.updateCursorCoordinates(true);
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -415,7 +494,11 @@ define([
|
||||
this.saveState = utils.debounce(function() {
|
||||
redoStack = [];
|
||||
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);
|
||||
// Limit the size of the stack
|
||||
while(undoStack.length > 100) {
|
||||
@ -441,11 +524,12 @@ define([
|
||||
this.onButtonStateChange();
|
||||
}, this);
|
||||
this.saveSelectionState = _.debounce(function() {
|
||||
// Should happen just after saveState
|
||||
if(this.currentMode === undefined) {
|
||||
selectionStartBefore = selectionMgr.selectionStart;
|
||||
selectionEndBefore = selectionMgr.selectionEnd;
|
||||
}
|
||||
}, 10);
|
||||
}, 50);
|
||||
this.canUndo = function() {
|
||||
return undoStack.length;
|
||||
};
|
||||
@ -462,7 +546,7 @@ define([
|
||||
}
|
||||
selectionMgr.setSelectionStartEnd(selectionStart, selectionEnd);
|
||||
selectionMgr.updateSelectionRange();
|
||||
selectionMgr.updateCursorCoordinates();
|
||||
selectionMgr.updateCursorCoordinates(true);
|
||||
var discussionListJSON = fileDesc.discussionListJSON;
|
||||
if(discussionListJSON != state.discussionListJSON) {
|
||||
var oldDiscussionList = fileDesc.discussionList;
|
||||
@ -767,6 +851,12 @@ define([
|
||||
.on('cut', function() {
|
||||
undoMgr.currentMode = 'cut';
|
||||
adjustCursorPosition();
|
||||
})
|
||||
.on('focus', function() {
|
||||
selectionMgr.hasFocus = true;
|
||||
})
|
||||
.on('blur', function() {
|
||||
selectionMgr.hasFocus = false;
|
||||
});
|
||||
|
||||
var action = function(action, options) {
|
||||
|
@ -1,307 +1,319 @@
|
||||
define([
|
||||
"jquery",
|
||||
"underscore",
|
||||
"crel",
|
||||
"utils",
|
||||
"logger",
|
||||
"classes/Extension",
|
||||
"settings",
|
||||
"text!html/settingsExtensionsAccordion.html",
|
||||
"extensions/yamlFrontMatterParser",
|
||||
"extensions/markdownSectionParser",
|
||||
"extensions/partialRendering",
|
||||
"extensions/buttonMarkdownSyntax",
|
||||
"extensions/googleAnalytics",
|
||||
"extensions/twitter",
|
||||
"extensions/dialogAbout",
|
||||
"extensions/dialogManagePublication",
|
||||
"extensions/dialogManageSynchronization",
|
||||
"extensions/dialogManageSharing",
|
||||
"extensions/dialogOpenHarddrive",
|
||||
"extensions/documentTitle",
|
||||
"extensions/documentSelector",
|
||||
"extensions/documentPanel",
|
||||
"extensions/documentManager",
|
||||
"extensions/workingIndicator",
|
||||
"extensions/notifications",
|
||||
"jquery",
|
||||
"underscore",
|
||||
"crel",
|
||||
"mousetrap",
|
||||
"utils",
|
||||
"logger",
|
||||
"classes/Extension",
|
||||
"settings",
|
||||
"text!html/settingsExtensionsAccordion.html",
|
||||
"extensions/yamlFrontMatterParser",
|
||||
"extensions/markdownSectionParser",
|
||||
"extensions/partialRendering",
|
||||
"extensions/buttonMarkdownSyntax",
|
||||
"extensions/googleAnalytics",
|
||||
"extensions/twitter",
|
||||
"extensions/dialogAbout",
|
||||
"extensions/dialogManagePublication",
|
||||
"extensions/dialogManageSynchronization",
|
||||
"extensions/dialogManageSharing",
|
||||
"extensions/dialogOpenHarddrive",
|
||||
"extensions/documentTitle",
|
||||
"extensions/documentSelector",
|
||||
"extensions/documentPanel",
|
||||
"extensions/documentManager",
|
||||
"extensions/workingIndicator",
|
||||
"extensions/notifications",
|
||||
"extensions/umlDiagrams",
|
||||
"extensions/markdownExtra",
|
||||
"extensions/toc",
|
||||
"extensions/mathJax",
|
||||
"extensions/emailConverter",
|
||||
"extensions/scrollSync",
|
||||
"extensions/buttonSync",
|
||||
"extensions/buttonPublish",
|
||||
"extensions/buttonStat",
|
||||
"extensions/buttonHtmlCode",
|
||||
"extensions/buttonViewer",
|
||||
"extensions/welcomeTour",
|
||||
"extensions/shortcuts",
|
||||
"extensions/userCustom",
|
||||
"extensions/comments",
|
||||
"extensions/htmlSanitizer",
|
||||
"bootstrap",
|
||||
"jquery-waitforimages"
|
||||
], function($, _, crel, utils, logger, Extension, settings, settingsExtensionsAccordionHTML) {
|
||||
"extensions/markdownExtra",
|
||||
"extensions/toc",
|
||||
"extensions/mathJax",
|
||||
"extensions/emailConverter",
|
||||
"extensions/scrollSync",
|
||||
"extensions/buttonSync",
|
||||
"extensions/buttonPublish",
|
||||
"extensions/buttonStat",
|
||||
"extensions/buttonHtmlCode",
|
||||
"extensions/buttonViewer",
|
||||
"extensions/welcomeTour",
|
||||
"extensions/shortcuts",
|
||||
"extensions/userCustom",
|
||||
"extensions/comments",
|
||||
"extensions/findReplace",
|
||||
"extensions/htmlSanitizer",
|
||||
"bootstrap",
|
||||
"jquery-waitforimages"
|
||||
], function($, _, crel, mousetrap, utils, logger, Extension, settings, settingsExtensionsAccordionHTML) {
|
||||
|
||||
var eventMgr = {};
|
||||
var eventMgr = {};
|
||||
|
||||
// Create a list of extensions from module arguments
|
||||
var extensionList = _.chain(arguments).map(function(argument) {
|
||||
return argument instanceof Extension && argument;
|
||||
}).compact().value();
|
||||
// Create a list of extensions from module arguments
|
||||
var extensionList = _.chain(arguments).map(function(argument) {
|
||||
return argument instanceof Extension && argument;
|
||||
}).compact().value();
|
||||
|
||||
// Configure extensions
|
||||
var extensionSettings = settings.extensionSettings || {};
|
||||
_.each(extensionList, function(extension) {
|
||||
// Set the extension.config attribute from settings or default
|
||||
// configuration
|
||||
extension.config = _.extend({}, extension.defaultConfig, extensionSettings[extension.extensionId]);
|
||||
if(window.viewerMode === true && extension.disableInViewer === true) {
|
||||
// Skip enabling the extension if we are in the viewer and extension
|
||||
// doesn't support it
|
||||
extension.enabled = false;
|
||||
}
|
||||
else {
|
||||
// Enable the extension if it's not optional or it has not been
|
||||
// disabled by the user
|
||||
extension.enabled = !extension.isOptional || extension.config.enabled === undefined || extension.config.enabled === true;
|
||||
}
|
||||
});
|
||||
// Configure extensions
|
||||
var extensionSettings = settings.extensionSettings || {};
|
||||
_.each(extensionList, function(extension) {
|
||||
// Set the extension.config attribute from settings or default
|
||||
// configuration
|
||||
extension.config = _.extend({}, extension.defaultConfig, extensionSettings[extension.extensionId]);
|
||||
if(window.viewerMode === true && extension.disableInViewer === true) {
|
||||
// Skip enabling the extension if we are in the viewer and extension
|
||||
// doesn't support it
|
||||
extension.enabled = false;
|
||||
}
|
||||
else {
|
||||
// Enable the extension if it's not optional or it has not been
|
||||
// disabled by the user
|
||||
extension.enabled = !extension.isOptional || extension.config.enabled === undefined || extension.config.enabled === true;
|
||||
}
|
||||
});
|
||||
|
||||
// Returns all listeners with the specified name that are implemented in the
|
||||
// enabled extensions
|
||||
function getExtensionListenerList(eventName) {
|
||||
return _.chain(extensionList).map(function(extension) {
|
||||
return extension.enabled && extension[eventName];
|
||||
}).compact().value();
|
||||
}
|
||||
// Returns all listeners with the specified name that are implemented in the
|
||||
// enabled extensions
|
||||
function getExtensionListenerList(eventName) {
|
||||
return _.chain(extensionList).map(function(extension) {
|
||||
return extension.enabled && extension[eventName];
|
||||
}).compact().value();
|
||||
}
|
||||
|
||||
// Returns a function that calls every listeners with the specified name
|
||||
// from all enabled extensions
|
||||
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);
|
||||
}
|
||||
});
|
||||
};
|
||||
}
|
||||
// Returns a function that calls every listeners with the specified name
|
||||
// from all enabled extensions
|
||||
var eventListenerListMap = {};
|
||||
|
||||
// Declare an event Hook in the eventMgr that we can fire using eventMgr.eventName()
|
||||
function addEventHook(eventName) {
|
||||
eventMgr[eventName] = createEventHook(eventName);
|
||||
}
|
||||
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);
|
||||
}
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
// Used by external modules (not extensions) to listen to events
|
||||
eventMgr.addListener = function(eventName, listener) {
|
||||
try {
|
||||
eventListenerListMap[eventName].push(listener);
|
||||
}
|
||||
catch(e) {
|
||||
console.error('No event listener called ' + eventName);
|
||||
}
|
||||
};
|
||||
// Declare an event Hook in the eventMgr that we can fire using eventMgr.eventName()
|
||||
function addEventHook(eventName) {
|
||||
eventMgr[eventName] = createEventHook(eventName);
|
||||
}
|
||||
|
||||
// Call every onInit listeners (enabled extensions only)
|
||||
createEventHook("onInit")();
|
||||
// Used by external modules (not extensions) to listen to events
|
||||
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
|
||||
eventMgr.onLoadSettings = function() {
|
||||
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;
|
||||
});
|
||||
};
|
||||
// Call every onInit listeners (enabled extensions only)
|
||||
createEventHook("onInit")();
|
||||
|
||||
addEventHook("onMessage");
|
||||
addEventHook("onError");
|
||||
addEventHook("onOfflineChanged");
|
||||
addEventHook("onUserActive");
|
||||
addEventHook("onAsyncRunning");
|
||||
addEventHook("onPeriodicRun");
|
||||
// Load/Save extension config from/to settings
|
||||
eventMgr.onLoadSettings = function() {
|
||||
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;
|
||||
});
|
||||
};
|
||||
|
||||
// To access modules that are loaded after extensions
|
||||
addEventHook("onEditorCreated");
|
||||
addEventHook("onFileMgrCreated");
|
||||
addEventHook("onSynchronizerCreated");
|
||||
addEventHook("onPublisherCreated");
|
||||
addEventHook("onEventMgrCreated");
|
||||
addEventHook("onMessage");
|
||||
addEventHook("onError");
|
||||
addEventHook("onOfflineChanged");
|
||||
addEventHook("onUserActive");
|
||||
addEventHook("onAsyncRunning");
|
||||
addEventHook("onPeriodicRun");
|
||||
|
||||
// Operations on files
|
||||
addEventHook("onFileCreated");
|
||||
addEventHook("onFileDeleted");
|
||||
addEventHook("onFileSelected");
|
||||
addEventHook("onFileOpen");
|
||||
addEventHook("onFileClosed");
|
||||
addEventHook("onContentChanged");
|
||||
addEventHook("onTitleChanged");
|
||||
// To access modules that are loaded after extensions
|
||||
addEventHook("onEditorCreated");
|
||||
addEventHook("onFileMgrCreated");
|
||||
addEventHook("onSynchronizerCreated");
|
||||
addEventHook("onPublisherCreated");
|
||||
addEventHook("onEventMgrCreated");
|
||||
|
||||
// Operations on folders
|
||||
addEventHook("onFoldersChanged");
|
||||
// Operations on files
|
||||
addEventHook("onFileCreated");
|
||||
addEventHook("onFileDeleted");
|
||||
addEventHook("onFileSelected");
|
||||
addEventHook("onFileOpen");
|
||||
addEventHook("onFileClosed");
|
||||
addEventHook("onContentChanged");
|
||||
addEventHook("onTitleChanged");
|
||||
|
||||
// Sync events
|
||||
addEventHook("onSyncRunning");
|
||||
addEventHook("onSyncSuccess");
|
||||
addEventHook("onSyncImportSuccess");
|
||||
addEventHook("onSyncExportSuccess");
|
||||
addEventHook("onSyncRemoved");
|
||||
// Operations on folders
|
||||
addEventHook("onFoldersChanged");
|
||||
|
||||
// Publish events
|
||||
addEventHook("onPublishRunning");
|
||||
addEventHook("onPublishSuccess");
|
||||
addEventHook("onNewPublishSuccess");
|
||||
addEventHook("onPublishRemoved");
|
||||
// Sync events
|
||||
addEventHook("onSyncRunning");
|
||||
addEventHook("onSyncSuccess");
|
||||
addEventHook("onSyncImportSuccess");
|
||||
addEventHook("onSyncExportSuccess");
|
||||
addEventHook("onSyncRemoved");
|
||||
|
||||
// Operations on Layout
|
||||
addEventHook("onLayoutCreated");
|
||||
addEventHook("onLayoutResize");
|
||||
addEventHook("onExtensionButtonResize");
|
||||
// Publish events
|
||||
addEventHook("onPublishRunning");
|
||||
addEventHook("onPublishSuccess");
|
||||
addEventHook("onNewPublishSuccess");
|
||||
addEventHook("onPublishRemoved");
|
||||
|
||||
// Operations on editor
|
||||
addEventHook("onPagedownConfigure");
|
||||
addEventHook("onSectionsCreated");
|
||||
addEventHook("onCursorCoordinates");
|
||||
// Operations on Layout
|
||||
addEventHook("onLayoutCreated");
|
||||
addEventHook("onLayoutResize");
|
||||
addEventHook("onExtensionButtonResize");
|
||||
|
||||
// Operations on comments
|
||||
addEventHook("onDiscussionCreated");
|
||||
addEventHook("onDiscussionRemoved");
|
||||
addEventHook("onCommentsChanged");
|
||||
// Operations on editor
|
||||
addEventHook("onPagedownConfigure");
|
||||
addEventHook("onSectionsCreated");
|
||||
addEventHook("onCursorCoordinates");
|
||||
addEventHook("onEditorPopover");
|
||||
|
||||
// Refresh twitter buttons
|
||||
addEventHook("onTweet");
|
||||
// Operations on comments
|
||||
addEventHook("onDiscussionCreated");
|
||||
addEventHook("onDiscussionRemoved");
|
||||
addEventHook("onCommentsChanged");
|
||||
|
||||
// Refresh twitter buttons
|
||||
addEventHook("onTweet");
|
||||
|
||||
|
||||
var onPreviewFinished = createEventHook("onPreviewFinished");
|
||||
var onAsyncPreviewListenerList = getExtensionListenerList("onAsyncPreview");
|
||||
var previewContentsElt;
|
||||
var $previewContentsElt;
|
||||
eventMgr.onAsyncPreview = function() {
|
||||
logger.log("onAsyncPreview");
|
||||
function recursiveCall(callbackList) {
|
||||
var callback = callbackList.length ? callbackList.shift() : function() {
|
||||
setTimeout(function() {
|
||||
var html = "";
|
||||
_.each(previewContentsElt.children, function(elt) {
|
||||
html += elt.innerHTML;
|
||||
});
|
||||
var htmlWithComments = utils.trim(html);
|
||||
var htmlWithoutComments = htmlWithComments.replace(/ <span class="comment label label-danger">.*?<\/span> /g, '');
|
||||
onPreviewFinished(htmlWithComments, htmlWithoutComments);
|
||||
}, 10);
|
||||
};
|
||||
callback(function() {
|
||||
recursiveCall(callbackList);
|
||||
});
|
||||
}
|
||||
recursiveCall(onAsyncPreviewListenerList.concat([function(callback) {
|
||||
// We assume some images are loading asynchronously after the preview
|
||||
$previewContentsElt.waitForImages(callback);
|
||||
}]));
|
||||
};
|
||||
var onPreviewFinished = createEventHook("onPreviewFinished");
|
||||
var onAsyncPreviewListenerList = getExtensionListenerList("onAsyncPreview");
|
||||
var previewContentsElt;
|
||||
var $previewContentsElt;
|
||||
eventMgr.onAsyncPreview = function() {
|
||||
logger.log("onAsyncPreview");
|
||||
function recursiveCall(callbackList) {
|
||||
var callback = callbackList.length ? callbackList.shift() : function() {
|
||||
setTimeout(function() {
|
||||
var html = "";
|
||||
_.each(previewContentsElt.children, function(elt) {
|
||||
html += elt.innerHTML;
|
||||
});
|
||||
var htmlWithComments = utils.trim(html);
|
||||
var htmlWithoutComments = htmlWithComments.replace(/ <span class="comment label label-danger">.*?<\/span> /g, '');
|
||||
onPreviewFinished(htmlWithComments, htmlWithoutComments);
|
||||
}, 10);
|
||||
};
|
||||
callback(function() {
|
||||
recursiveCall(callbackList);
|
||||
});
|
||||
}
|
||||
|
||||
var onReady = createEventHook("onReady");
|
||||
eventMgr.onReady = function() {
|
||||
previewContentsElt = document.getElementById('preview-contents');
|
||||
$previewContentsElt = $(previewContentsElt);
|
||||
recursiveCall(onAsyncPreviewListenerList.concat([
|
||||
function(callback) {
|
||||
// We assume some images are loading asynchronously after the preview
|
||||
$previewContentsElt.waitForImages(callback);
|
||||
}
|
||||
]));
|
||||
};
|
||||
|
||||
// Create a button from an extension listener
|
||||
var createBtn = function(listener) {
|
||||
var buttonGrpElt = crel('div', {
|
||||
class: 'btn-group'
|
||||
});
|
||||
var btnElt = listener();
|
||||
if(_.isString(btnElt)) {
|
||||
buttonGrpElt.innerHTML = btnElt;
|
||||
}
|
||||
else if(_.isElement(btnElt)) {
|
||||
buttonGrpElt.appendChild(btnElt);
|
||||
}
|
||||
return buttonGrpElt;
|
||||
};
|
||||
var onReady = createEventHook("onReady");
|
||||
eventMgr.onReady = function() {
|
||||
previewContentsElt = document.getElementById('preview-contents');
|
||||
$previewContentsElt = $(previewContentsElt);
|
||||
|
||||
if(window.viewerMode === false) {
|
||||
// Create accordion in settings dialog
|
||||
var accordionHtml = _.chain(extensionList).sortBy(function(extension) {
|
||||
return extension.extensionName.toLowerCase();
|
||||
}).reduce(function(html, extension) {
|
||||
return html + (extension.settingsBlock ? _.template(settingsExtensionsAccordionHTML, {
|
||||
extensionId: extension.extensionId,
|
||||
extensionName: extension.extensionName,
|
||||
isOptional: extension.isOptional,
|
||||
settingsBlock: extension.settingsBlock
|
||||
}) : "");
|
||||
}, "").value();
|
||||
document.querySelector('.accordion-extensions').innerHTML = accordionHtml;
|
||||
// Create a button from an extension listener
|
||||
var createBtn = function(listener) {
|
||||
var buttonGrpElt = crel('div', {
|
||||
class: 'btn-group'
|
||||
});
|
||||
var btnElt = listener();
|
||||
if(_.isString(btnElt)) {
|
||||
buttonGrpElt.innerHTML = btnElt;
|
||||
}
|
||||
else if(_.isElement(btnElt)) {
|
||||
buttonGrpElt.appendChild(btnElt);
|
||||
}
|
||||
return buttonGrpElt;
|
||||
};
|
||||
|
||||
// Create extension buttons
|
||||
logger.log("onCreateButton");
|
||||
var onCreateButtonListenerList = getExtensionListenerList("onCreateButton");
|
||||
var extensionButtonsFragment = document.createDocumentFragment();
|
||||
_.each(onCreateButtonListenerList, function(listener) {
|
||||
extensionButtonsFragment.appendChild(createBtn(listener));
|
||||
});
|
||||
document.querySelector('.extension-buttons').appendChild(extensionButtonsFragment);
|
||||
}
|
||||
if(window.viewerMode === false) {
|
||||
// Create accordion in settings dialog
|
||||
var accordionHtml = _.chain(extensionList).sortBy(function(extension) {
|
||||
return extension.extensionName.toLowerCase();
|
||||
}).reduce(function(html, extension) {
|
||||
return html + (extension.settingsBlock ? _.template(settingsExtensionsAccordionHTML, {
|
||||
extensionId: extension.extensionId,
|
||||
extensionName: extension.extensionName,
|
||||
isOptional: extension.isOptional,
|
||||
settingsBlock: extension.settingsBlock
|
||||
}) : "");
|
||||
}, "").value();
|
||||
document.querySelector('.accordion-extensions').innerHTML = accordionHtml;
|
||||
|
||||
// Create extension preview buttons
|
||||
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);
|
||||
// Create extension buttons
|
||||
logger.log("onCreateButton");
|
||||
var onCreateButtonListenerList = getExtensionListenerList("onCreateButton");
|
||||
var extensionButtonsFragment = document.createDocumentFragment();
|
||||
_.each(onCreateButtonListenerList, function(listener) {
|
||||
extensionButtonsFragment.appendChild(createBtn(listener));
|
||||
});
|
||||
document.querySelector('.extension-buttons').appendChild(extensionButtonsFragment);
|
||||
}
|
||||
|
||||
// Call onReady listeners
|
||||
onReady();
|
||||
};
|
||||
// Create extension preview buttons
|
||||
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
|
||||
eventMgr.onEventMgrCreated(eventMgr);
|
||||
return eventMgr;
|
||||
// Shall close every popover
|
||||
mousetrap.bind('escape', function() {
|
||||
eventMgr.onEditorPopover();
|
||||
});
|
||||
|
||||
// Call onReady listeners
|
||||
onReady();
|
||||
};
|
||||
|
||||
// For extensions that need to call other extensions
|
||||
eventMgr.onEventMgrCreated(eventMgr);
|
||||
return eventMgr;
|
||||
});
|
||||
|
@ -226,6 +226,12 @@ define([
|
||||
currentContext && currentContext.$commentElt.popover('toggle').popover('destroy');
|
||||
}
|
||||
|
||||
comments.onEditorPopover = function() {
|
||||
closeCurrentPopover();
|
||||
editor.focus();
|
||||
editor.adjustCursorPosition();
|
||||
};
|
||||
|
||||
comments.onDiscussionCreated = function(fileDesc) {
|
||||
currentFileDesc === fileDesc && refreshDiscussions();
|
||||
};
|
||||
@ -265,7 +271,7 @@ define([
|
||||
}
|
||||
|
||||
comments.onReady = function() {
|
||||
cssApplier = rangy.createCssClassApplier("comment-highlight", {
|
||||
cssApplier = rangy.createCssClassApplier('comment-highlight', {
|
||||
normalize: false
|
||||
});
|
||||
var previousContent = '';
|
||||
@ -308,7 +314,7 @@ define([
|
||||
selector: '#wmd-input > .editor-margin > .discussion'
|
||||
});
|
||||
$(marginElt).on('show.bs.popover', function(evt) {
|
||||
closeCurrentPopover();
|
||||
eventMgr.onEditorPopover();
|
||||
var context = new Context(evt.target, currentFileDesc);
|
||||
currentContext = context;
|
||||
|
||||
@ -348,18 +354,10 @@ define([
|
||||
|
||||
var $addButton = $(popoverElt.querySelector('.action-add-comment'));
|
||||
$().add(context.$contentInputElt).add(context.$authorInputElt).keydown(function(evt) {
|
||||
// Enter key
|
||||
switch(evt.which) {
|
||||
case 13:
|
||||
evt.preventDefault();
|
||||
$addButton.click();
|
||||
return;
|
||||
case 27:
|
||||
evt.preventDefault();
|
||||
closeCurrentPopover();
|
||||
editor.focus();
|
||||
editor.adjustCursorPosition();
|
||||
return;
|
||||
if(evt.which === 13) {
|
||||
// Enter key
|
||||
evt.preventDefault();
|
||||
$addButton.click();
|
||||
}
|
||||
});
|
||||
$addButton.click(function(evt) {
|
||||
@ -418,7 +416,7 @@ define([
|
||||
context.rangyRange = rangy.createRange();
|
||||
context.rangyRange.setStart(context.selectionRange.startContainer, context.selectionRange.startOffset);
|
||||
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) {
|
||||
cssApplier.applyToRange(context.rangyRange);
|
||||
}
|
||||
@ -452,7 +450,6 @@ define([
|
||||
evt.stopPropagation();
|
||||
});
|
||||
|
||||
|
||||
var $newCommentElt = $(newCommentElt);
|
||||
$openDiscussionElt = $('.button-open-discussion').click(function(evt) {
|
||||
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>
|
||||
</button>
|
||||
<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>
|
||||
</button>
|
||||
<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>
|
||||
<span class="value"></span>
|
||||
</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>
|
||||
</button>
|
||||
<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">
|
||||
<i class="icon-resize-full"></i>
|
||||
</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-border: fade(@secondary, 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-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-bg: @transparent;
|
||||
@btn-info-border: @transparent;
|
||||
@ -130,10 +130,10 @@
|
||||
@popover-arrow-outer-color: @secondary-border-color;
|
||||
@popover-title-bg: @transparent;
|
||||
@alert-border-radius: 0;
|
||||
@label-warning-bg: spin(darken(@logo-yellow, 4%), -10);
|
||||
@state-warning-text: spin(darken(@logo-yellow, 14%), -10);
|
||||
@state-warning-bg: fade(spin(@logo-yellow, -10), 12%);
|
||||
@state-warning-border: fade(spin(@logo-yellow, -10), 24%);
|
||||
@label-warning-bg: spin(darken(@logo-yellow, 4%), -5);
|
||||
@state-warning-text: spin(darken(@logo-yellow, 14%), -5);
|
||||
@state-warning-bg: fade(spin(@logo-yellow, -5), 12%);
|
||||
@state-warning-border: fade(spin(@logo-yellow, -5), 24%);
|
||||
@label-danger-bg: spin(darken(@logo-orange, 4%), -8);
|
||||
@state-danger-text: spin(darken(@logo-orange, 18%), -8);
|
||||
@state-danger-bg: fade(spin(@logo-orange, -8), 10%);
|
||||
@ -162,7 +162,7 @@ body {
|
||||
.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));
|
||||
}
|
||||
|
||||
@ -699,7 +699,6 @@ a {
|
||||
padding: 15px 20px;
|
||||
z-index: 3;
|
||||
border: 1px solid @secondary-border-color;
|
||||
border-top: 0;
|
||||
border-radius: 6px;
|
||||
.nav {
|
||||
margin-bottom: 10px;
|
||||
@ -736,7 +735,7 @@ a {
|
||||
// Dropdown document selector
|
||||
.dropdown-file-selector {
|
||||
top: 6px;
|
||||
right: 30px;
|
||||
right: 55px;
|
||||
left: auto;
|
||||
margin: 0;
|
||||
min-width: 280px;
|
||||
@ -830,8 +829,8 @@ a {
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
z-index: 40;
|
||||
background-color: @btn-info-hover-bg;
|
||||
border: 1px solid @btn-info-hover-border;
|
||||
background-color: @navbar-default-bg;
|
||||
border: 1px solid @navbar-default-border;
|
||||
border-radius: @border-radius-base;
|
||||
cursor: move;
|
||||
|
||||
@ -880,10 +879,7 @@ a {
|
||||
}
|
||||
|
||||
.drag-me {
|
||||
color: @btn-info-color;
|
||||
&.info-tooltip {
|
||||
color: @btn-info-hover-color;
|
||||
}
|
||||
color: @btn-success-color;
|
||||
i:before {
|
||||
width: 5px;
|
||||
}
|
||||
@ -1124,9 +1120,9 @@ a {
|
||||
}
|
||||
}
|
||||
&.added {
|
||||
color: fade(@label-warning-bg, 50%);
|
||||
color: fade(@label-warning-bg, 70%);
|
||||
&:hover, &.active, &.active:hover {
|
||||
color: fade(@label-warning-bg, 80%) !important;
|
||||
color: fade(@label-warning-bg, 100%) !important;
|
||||
}
|
||||
}
|
||||
&.replied {
|
||||
@ -1159,6 +1155,14 @@ a {
|
||||
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 {
|
||||
font-weight: bold;
|
||||
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
|
||||
|
Loading…
Reference in New Issue
Block a user