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 = offsetList.shift();
|
||||||
}
|
}
|
||||||
offset -= text.length;
|
walkerOffset = newWalkerOffset;
|
||||||
}
|
}
|
||||||
return {
|
do {
|
||||||
container: walker.currentNode,
|
result.push({
|
||||||
offset: text.length
|
container: walker.currentNode,
|
||||||
};
|
offsetInContainer: walkerOffset,
|
||||||
|
offset: offset
|
||||||
|
});
|
||||||
|
offset = offsetList.shift();
|
||||||
|
}
|
||||||
|
while(offset);
|
||||||
|
return result;
|
||||||
};
|
};
|
||||||
this.createRange = function(start, end) {
|
this.createRange = function(start, end) {
|
||||||
start = start < 0 ? 0 : start;
|
start = start < 0 ? 0 : start;
|
||||||
end = end < 0 ? 0 : end;
|
end = end < 0 ? 0 : end;
|
||||||
var range = document.createRange();
|
var range = document.createRange();
|
||||||
var offset = _.isObject(start) ? start : this.findOffset(start);
|
var offsetList = [];
|
||||||
range.setStart(offset.container, offset.offset);
|
if(_.isNumber(start)) {
|
||||||
if(end && end != start) {
|
offsetList.push(start);
|
||||||
offset = _.isObject(end) ? end : this.findOffset(end);
|
start = offsetList.length - 1;
|
||||||
}
|
}
|
||||||
range.setEnd(offset.container, offset.offset);
|
if(_.isNumber(end)) {
|
||||||
|
offsetList.push(end);
|
||||||
|
end = offsetList.length - 1;
|
||||||
|
}
|
||||||
|
offsetList = this.findOffsets(offsetList);
|
||||||
|
var startOffset = _.isObject(start) ? start : offsetList[start];
|
||||||
|
range.setStart(startOffset.container, startOffset.offsetInContainer);
|
||||||
|
var endOffset = startOffset;
|
||||||
|
if(end && end != start) {
|
||||||
|
endOffset = _.isObject(end) ? end : offsetList[end];
|
||||||
|
}
|
||||||
|
range.setEnd(endOffset.container, endOffset.offsetInContainer);
|
||||||
return range;
|
return range;
|
||||||
};
|
};
|
||||||
var adjustScroll;
|
var adjustScroll;
|
||||||
@ -154,15 +185,20 @@ define([
|
|||||||
if(this.cursorY !== coordinates.y) {
|
if(this.cursorY !== coordinates.y) {
|
||||||
this.cursorY = coordinates.y;
|
this.cursorY = coordinates.y;
|
||||||
eventMgr.onCursorCoordinates(coordinates.x, coordinates.y);
|
eventMgr.onCursorCoordinates(coordinates.x, coordinates.y);
|
||||||
if(adjustScroll && settings.cursorFocusRatio) {
|
if(adjustScroll) {
|
||||||
var adjust = inputElt.offsetHeight / 2 * settings.cursorFocusRatio;
|
var adjustTop, adjustBottom;
|
||||||
var cursorMinY = inputElt.scrollTop + adjust;
|
adjustTop = adjustBottom = inputElt.offsetHeight / 2 * settings.cursorFocusRatio;
|
||||||
var cursorMaxY = inputElt.scrollTop + inputElt.offsetHeight - adjust;
|
adjustTop = this.adjustTop || adjustTop;
|
||||||
if(selectionMgr.cursorY < cursorMinY) {
|
adjustBottom = this.adjustBottom || adjustTop;
|
||||||
inputElt.scrollTop += selectionMgr.cursorY - cursorMinY;
|
if(adjustTop && adjustBottom) {
|
||||||
}
|
var cursorMinY = inputElt.scrollTop + adjustTop;
|
||||||
else if(selectionMgr.cursorY > cursorMaxY) {
|
var cursorMaxY = inputElt.scrollTop + inputElt.offsetHeight - adjustBottom;
|
||||||
inputElt.scrollTop += selectionMgr.cursorY - cursorMaxY;
|
if(selectionMgr.cursorY < cursorMinY) {
|
||||||
|
inputElt.scrollTop += selectionMgr.cursorY - cursorMinY;
|
||||||
|
}
|
||||||
|
else if(selectionMgr.cursorY > cursorMaxY) {
|
||||||
|
inputElt.scrollTop += selectionMgr.cursorY - cursorMaxY;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -253,11 +289,16 @@ define([
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
})();
|
})();
|
||||||
this.getCoordinates = function(inputOffset, container, offset) {
|
this.getSelectedText = function() {
|
||||||
|
var min = Math.min(this.selectionStart, this.selectionEnd);
|
||||||
|
var max = Math.max(this.selectionStart, this.selectionEnd);
|
||||||
|
return textContent.substring(min, max);
|
||||||
|
};
|
||||||
|
this.getCoordinates = function(inputOffset, container, offsetInContainer) {
|
||||||
if(!container) {
|
if(!container) {
|
||||||
offset = this.findOffset(inputOffset);
|
var offset = this.findOffsets([inputOffset])[0];
|
||||||
container = offset.container;
|
container = offset.container;
|
||||||
offset = offset.offset;
|
offsetInContainer = offset.offsetInContainer;
|
||||||
}
|
}
|
||||||
var x = 0;
|
var x = 0;
|
||||||
var y = 0;
|
var y = 0;
|
||||||
@ -268,26 +309,30 @@ define([
|
|||||||
var selectedChar = textContent[inputOffset];
|
var selectedChar = textContent[inputOffset];
|
||||||
var startOffset = {
|
var startOffset = {
|
||||||
container: container,
|
container: container,
|
||||||
offset: offset
|
offsetInContainer: offsetInContainer,
|
||||||
|
offset: inputOffset
|
||||||
};
|
};
|
||||||
var endOffset = {
|
var endOffset = {
|
||||||
container: container,
|
container: container,
|
||||||
offset: offset
|
offsetInContainer: offsetInContainer,
|
||||||
|
offset: inputOffset
|
||||||
};
|
};
|
||||||
if(inputOffset > 0 && (selectedChar === undefined || selectedChar == '\n')) {
|
if(inputOffset > 0 && (selectedChar === undefined || selectedChar == '\n')) {
|
||||||
if(startOffset.offset === 0) {
|
if(startOffset.offset === 0) {
|
||||||
|
// Need to calculate offset-1
|
||||||
startOffset = inputOffset - 1;
|
startOffset = inputOffset - 1;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
startOffset.offset -= 1;
|
startOffset.offsetInContainer -= 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
if(endOffset.offset === container.textContent.length) {
|
if(endOffset.offset === container.textContent.length) {
|
||||||
|
// Need to calculate offset+1
|
||||||
endOffset = inputOffset + 1;
|
endOffset = inputOffset + 1;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
endOffset.offset += 1;
|
endOffset.offsetInContainer += 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
var selectionRange = this.createRange(startOffset, endOffset);
|
var selectionRange = this.createRange(startOffset, endOffset);
|
||||||
@ -351,10 +396,44 @@ define([
|
|||||||
range.deleteContents();
|
range.deleteContents();
|
||||||
range.insertNode(document.createTextNode(replacement));
|
range.insertNode(document.createTextNode(replacement));
|
||||||
range.detach();
|
range.detach();
|
||||||
|
return {
|
||||||
|
start: startOffset,
|
||||||
|
end: value.length - endOffset
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
editor.setValue = setValue;
|
editor.setValue = setValue;
|
||||||
|
|
||||||
|
function replace(selectionStart, selectionEnd, replacement) {
|
||||||
|
undoMgr.currentMode = undoMgr.currentMode || 'replace';
|
||||||
|
var range = selectionMgr.createRange(selectionStart, selectionEnd);
|
||||||
|
if('' + range == replacement) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
range.deleteContents();
|
||||||
|
range.insertNode(document.createTextNode(replacement));
|
||||||
|
range.detach();
|
||||||
|
var endOffset = selectionStart + replacement.length;
|
||||||
|
selectionMgr.setSelectionStartEnd(endOffset, endOffset);
|
||||||
|
selectionMgr.updateSelectionRange();
|
||||||
|
selectionMgr.updateCursorCoordinates(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
editor.replace = replace;
|
||||||
|
|
||||||
|
function replaceAll(search, replacement) {
|
||||||
|
undoMgr.currentMode = undoMgr.currentMode || 'replace';
|
||||||
|
var value = textContent.replace(search, replacement);
|
||||||
|
if(value != textContent) {
|
||||||
|
var offset = editor.setValue(value);
|
||||||
|
selectionMgr.setSelectionStartEnd(offset.end, offset.end);
|
||||||
|
selectionMgr.updateSelectionRange();
|
||||||
|
selectionMgr.updateCursorCoordinates(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
editor.replaceAll = replaceAll;
|
||||||
|
|
||||||
function replacePreviousText(text, replacement) {
|
function replacePreviousText(text, replacement) {
|
||||||
var offset = selectionMgr.selectionStart;
|
var offset = selectionMgr.selectionStart;
|
||||||
if(offset !== selectionMgr.selectionEnd) {
|
if(offset !== selectionMgr.selectionEnd) {
|
||||||
@ -370,7 +449,7 @@ define([
|
|||||||
offset = offset - text.length + replacement.length;
|
offset = offset - text.length + replacement.length;
|
||||||
selectionMgr.setSelectionStartEnd(offset, offset);
|
selectionMgr.setSelectionStartEnd(offset, offset);
|
||||||
selectionMgr.updateSelectionRange();
|
selectionMgr.updateSelectionRange();
|
||||||
selectionMgr.updateCursorCoordinates();
|
selectionMgr.updateCursorCoordinates(true);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -415,7 +494,11 @@ define([
|
|||||||
this.saveState = utils.debounce(function() {
|
this.saveState = utils.debounce(function() {
|
||||||
redoStack = [];
|
redoStack = [];
|
||||||
var currentTime = Date.now();
|
var currentTime = Date.now();
|
||||||
if(this.currentMode == 'comment' || lastMode == 'newlines' || this.currentMode != lastMode || currentTime - lastTime > 1000) {
|
if(this.currentMode == 'comment' ||
|
||||||
|
this.currentMode == 'replace' ||
|
||||||
|
lastMode == 'newlines' ||
|
||||||
|
this.currentMode != lastMode ||
|
||||||
|
currentTime - lastTime > 1000) {
|
||||||
undoStack.push(currentState);
|
undoStack.push(currentState);
|
||||||
// Limit the size of the stack
|
// Limit the size of the stack
|
||||||
while(undoStack.length > 100) {
|
while(undoStack.length > 100) {
|
||||||
@ -441,11 +524,12 @@ define([
|
|||||||
this.onButtonStateChange();
|
this.onButtonStateChange();
|
||||||
}, this);
|
}, this);
|
||||||
this.saveSelectionState = _.debounce(function() {
|
this.saveSelectionState = _.debounce(function() {
|
||||||
|
// Should happen just after saveState
|
||||||
if(this.currentMode === undefined) {
|
if(this.currentMode === undefined) {
|
||||||
selectionStartBefore = selectionMgr.selectionStart;
|
selectionStartBefore = selectionMgr.selectionStart;
|
||||||
selectionEndBefore = selectionMgr.selectionEnd;
|
selectionEndBefore = selectionMgr.selectionEnd;
|
||||||
}
|
}
|
||||||
}, 10);
|
}, 50);
|
||||||
this.canUndo = function() {
|
this.canUndo = function() {
|
||||||
return undoStack.length;
|
return undoStack.length;
|
||||||
};
|
};
|
||||||
@ -462,7 +546,7 @@ define([
|
|||||||
}
|
}
|
||||||
selectionMgr.setSelectionStartEnd(selectionStart, selectionEnd);
|
selectionMgr.setSelectionStartEnd(selectionStart, selectionEnd);
|
||||||
selectionMgr.updateSelectionRange();
|
selectionMgr.updateSelectionRange();
|
||||||
selectionMgr.updateCursorCoordinates();
|
selectionMgr.updateCursorCoordinates(true);
|
||||||
var discussionListJSON = fileDesc.discussionListJSON;
|
var discussionListJSON = fileDesc.discussionListJSON;
|
||||||
if(discussionListJSON != state.discussionListJSON) {
|
if(discussionListJSON != state.discussionListJSON) {
|
||||||
var oldDiscussionList = fileDesc.discussionList;
|
var oldDiscussionList = fileDesc.discussionList;
|
||||||
@ -767,6 +851,12 @@ define([
|
|||||||
.on('cut', function() {
|
.on('cut', function() {
|
||||||
undoMgr.currentMode = 'cut';
|
undoMgr.currentMode = 'cut';
|
||||||
adjustCursorPosition();
|
adjustCursorPosition();
|
||||||
|
})
|
||||||
|
.on('focus', function() {
|
||||||
|
selectionMgr.hasFocus = true;
|
||||||
|
})
|
||||||
|
.on('blur', function() {
|
||||||
|
selectionMgr.hasFocus = false;
|
||||||
});
|
});
|
||||||
|
|
||||||
var action = function(action, options) {
|
var action = function(action, options) {
|
||||||
|
@ -1,307 +1,319 @@
|
|||||||
define([
|
define([
|
||||||
"jquery",
|
"jquery",
|
||||||
"underscore",
|
"underscore",
|
||||||
"crel",
|
"crel",
|
||||||
"utils",
|
"mousetrap",
|
||||||
"logger",
|
"utils",
|
||||||
"classes/Extension",
|
"logger",
|
||||||
"settings",
|
"classes/Extension",
|
||||||
"text!html/settingsExtensionsAccordion.html",
|
"settings",
|
||||||
"extensions/yamlFrontMatterParser",
|
"text!html/settingsExtensionsAccordion.html",
|
||||||
"extensions/markdownSectionParser",
|
"extensions/yamlFrontMatterParser",
|
||||||
"extensions/partialRendering",
|
"extensions/markdownSectionParser",
|
||||||
"extensions/buttonMarkdownSyntax",
|
"extensions/partialRendering",
|
||||||
"extensions/googleAnalytics",
|
"extensions/buttonMarkdownSyntax",
|
||||||
"extensions/twitter",
|
"extensions/googleAnalytics",
|
||||||
"extensions/dialogAbout",
|
"extensions/twitter",
|
||||||
"extensions/dialogManagePublication",
|
"extensions/dialogAbout",
|
||||||
"extensions/dialogManageSynchronization",
|
"extensions/dialogManagePublication",
|
||||||
"extensions/dialogManageSharing",
|
"extensions/dialogManageSynchronization",
|
||||||
"extensions/dialogOpenHarddrive",
|
"extensions/dialogManageSharing",
|
||||||
"extensions/documentTitle",
|
"extensions/dialogOpenHarddrive",
|
||||||
"extensions/documentSelector",
|
"extensions/documentTitle",
|
||||||
"extensions/documentPanel",
|
"extensions/documentSelector",
|
||||||
"extensions/documentManager",
|
"extensions/documentPanel",
|
||||||
"extensions/workingIndicator",
|
"extensions/documentManager",
|
||||||
"extensions/notifications",
|
"extensions/workingIndicator",
|
||||||
|
"extensions/notifications",
|
||||||
"extensions/umlDiagrams",
|
"extensions/umlDiagrams",
|
||||||
"extensions/markdownExtra",
|
"extensions/markdownExtra",
|
||||||
"extensions/toc",
|
"extensions/toc",
|
||||||
"extensions/mathJax",
|
"extensions/mathJax",
|
||||||
"extensions/emailConverter",
|
"extensions/emailConverter",
|
||||||
"extensions/scrollSync",
|
"extensions/scrollSync",
|
||||||
"extensions/buttonSync",
|
"extensions/buttonSync",
|
||||||
"extensions/buttonPublish",
|
"extensions/buttonPublish",
|
||||||
"extensions/buttonStat",
|
"extensions/buttonStat",
|
||||||
"extensions/buttonHtmlCode",
|
"extensions/buttonHtmlCode",
|
||||||
"extensions/buttonViewer",
|
"extensions/buttonViewer",
|
||||||
"extensions/welcomeTour",
|
"extensions/welcomeTour",
|
||||||
"extensions/shortcuts",
|
"extensions/shortcuts",
|
||||||
"extensions/userCustom",
|
"extensions/userCustom",
|
||||||
"extensions/comments",
|
"extensions/comments",
|
||||||
"extensions/htmlSanitizer",
|
"extensions/findReplace",
|
||||||
"bootstrap",
|
"extensions/htmlSanitizer",
|
||||||
"jquery-waitforimages"
|
"bootstrap",
|
||||||
], function($, _, crel, utils, logger, Extension, settings, settingsExtensionsAccordionHTML) {
|
"jquery-waitforimages"
|
||||||
|
], function($, _, crel, mousetrap, utils, logger, Extension, settings, settingsExtensionsAccordionHTML) {
|
||||||
|
|
||||||
var eventMgr = {};
|
var eventMgr = {};
|
||||||
|
|
||||||
// Create a list of extensions from module arguments
|
// Create a list of extensions from module arguments
|
||||||
var extensionList = _.chain(arguments).map(function(argument) {
|
var extensionList = _.chain(arguments).map(function(argument) {
|
||||||
return argument instanceof Extension && argument;
|
return argument instanceof Extension && argument;
|
||||||
}).compact().value();
|
}).compact().value();
|
||||||
|
|
||||||
// Configure extensions
|
// Configure extensions
|
||||||
var extensionSettings = settings.extensionSettings || {};
|
var extensionSettings = settings.extensionSettings || {};
|
||||||
_.each(extensionList, function(extension) {
|
_.each(extensionList, function(extension) {
|
||||||
// Set the extension.config attribute from settings or default
|
// Set the extension.config attribute from settings or default
|
||||||
// configuration
|
// configuration
|
||||||
extension.config = _.extend({}, extension.defaultConfig, extensionSettings[extension.extensionId]);
|
extension.config = _.extend({}, extension.defaultConfig, extensionSettings[extension.extensionId]);
|
||||||
if(window.viewerMode === true && extension.disableInViewer === true) {
|
if(window.viewerMode === true && extension.disableInViewer === true) {
|
||||||
// Skip enabling the extension if we are in the viewer and extension
|
// Skip enabling the extension if we are in the viewer and extension
|
||||||
// doesn't support it
|
// doesn't support it
|
||||||
extension.enabled = false;
|
extension.enabled = false;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
// Enable the extension if it's not optional or it has not been
|
// Enable the extension if it's not optional or it has not been
|
||||||
// disabled by the user
|
// disabled by the user
|
||||||
extension.enabled = !extension.isOptional || extension.config.enabled === undefined || extension.config.enabled === true;
|
extension.enabled = !extension.isOptional || extension.config.enabled === undefined || extension.config.enabled === true;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Returns all listeners with the specified name that are implemented in the
|
// Returns all listeners with the specified name that are implemented in the
|
||||||
// enabled extensions
|
// enabled extensions
|
||||||
function getExtensionListenerList(eventName) {
|
function getExtensionListenerList(eventName) {
|
||||||
return _.chain(extensionList).map(function(extension) {
|
return _.chain(extensionList).map(function(extension) {
|
||||||
return extension.enabled && extension[eventName];
|
return extension.enabled && extension[eventName];
|
||||||
}).compact().value();
|
}).compact().value();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Returns a function that calls every listeners with the specified name
|
// Returns a function that calls every listeners with the specified name
|
||||||
// from all enabled extensions
|
// from all enabled extensions
|
||||||
var eventListenerListMap = {};
|
var eventListenerListMap = {};
|
||||||
function createEventHook(eventName) {
|
|
||||||
eventListenerListMap[eventName] = getExtensionListenerList(eventName);
|
|
||||||
return function() {
|
|
||||||
logger.log(eventName, arguments);
|
|
||||||
var eventArguments = arguments;
|
|
||||||
_.each(eventListenerListMap[eventName], function(listener) {
|
|
||||||
// Use try/catch in case userCustom listener contains error
|
|
||||||
try {
|
|
||||||
listener.apply(null, eventArguments);
|
|
||||||
}
|
|
||||||
catch(e) {
|
|
||||||
console.error(_.isObject(e) ? e.stack : e);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// Declare an event Hook in the eventMgr that we can fire using eventMgr.eventName()
|
function createEventHook(eventName) {
|
||||||
function addEventHook(eventName) {
|
eventListenerListMap[eventName] = getExtensionListenerList(eventName);
|
||||||
eventMgr[eventName] = createEventHook(eventName);
|
return function() {
|
||||||
}
|
logger.log(eventName, arguments);
|
||||||
|
var eventArguments = arguments;
|
||||||
|
_.each(eventListenerListMap[eventName], function(listener) {
|
||||||
|
// Use try/catch in case userCustom listener contains error
|
||||||
|
try {
|
||||||
|
listener.apply(null, eventArguments);
|
||||||
|
}
|
||||||
|
catch(e) {
|
||||||
|
console.error(_.isObject(e) ? e.stack : e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
// Used by external modules (not extensions) to listen to events
|
// Declare an event Hook in the eventMgr that we can fire using eventMgr.eventName()
|
||||||
eventMgr.addListener = function(eventName, listener) {
|
function addEventHook(eventName) {
|
||||||
try {
|
eventMgr[eventName] = createEventHook(eventName);
|
||||||
eventListenerListMap[eventName].push(listener);
|
}
|
||||||
}
|
|
||||||
catch(e) {
|
|
||||||
console.error('No event listener called ' + eventName);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Call every onInit listeners (enabled extensions only)
|
// Used by external modules (not extensions) to listen to events
|
||||||
createEventHook("onInit")();
|
eventMgr.addListener = function(eventName, listener) {
|
||||||
|
try {
|
||||||
|
eventListenerListMap[eventName].push(listener);
|
||||||
|
}
|
||||||
|
catch(e) {
|
||||||
|
console.error('No event listener called ' + eventName);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// Load/Save extension config from/to settings
|
// Call every onInit listeners (enabled extensions only)
|
||||||
eventMgr.onLoadSettings = function() {
|
createEventHook("onInit")();
|
||||||
logger.log("onLoadSettings");
|
|
||||||
_.each(extensionList, function(extension) {
|
|
||||||
var isChecked = !extension.isOptional || extension.config.enabled === undefined || extension.config.enabled === true;
|
|
||||||
utils.setInputChecked("#input-enable-extension-" + extension.extensionId, isChecked);
|
|
||||||
// Special case for Markdown Extra and MathJax
|
|
||||||
if(extension.extensionId == 'markdownExtra') {
|
|
||||||
utils.setInputChecked("#input-settings-markdown-extra", isChecked);
|
|
||||||
}
|
|
||||||
else if(extension.extensionId == 'mathJax') {
|
|
||||||
utils.setInputChecked("#input-settings-mathjax", isChecked);
|
|
||||||
}
|
|
||||||
var onLoadSettingsListener = extension.onLoadSettings;
|
|
||||||
onLoadSettingsListener && onLoadSettingsListener();
|
|
||||||
});
|
|
||||||
};
|
|
||||||
eventMgr.onSaveSettings = function(newExtensionSettings, event) {
|
|
||||||
logger.log("onSaveSettings");
|
|
||||||
_.each(extensionList, function(extension) {
|
|
||||||
var newExtensionConfig = _.extend({}, extension.defaultConfig);
|
|
||||||
newExtensionConfig.enabled = utils.getInputChecked("#input-enable-extension-" + extension.extensionId);
|
|
||||||
var isChecked;
|
|
||||||
// Special case for Markdown Extra and MathJax
|
|
||||||
if(extension.extensionId == 'markdownExtra') {
|
|
||||||
isChecked = utils.getInputChecked("#input-settings-markdown-extra");
|
|
||||||
if(isChecked != extension.enabled) {
|
|
||||||
newExtensionConfig.enabled = isChecked;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if(extension.extensionId == 'mathJax') {
|
|
||||||
isChecked = utils.getInputChecked("#input-settings-mathjax");
|
|
||||||
if(isChecked != extension.enabled) {
|
|
||||||
newExtensionConfig.enabled = isChecked;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
var onSaveSettingsListener = extension.onSaveSettings;
|
|
||||||
onSaveSettingsListener && onSaveSettingsListener(newExtensionConfig, event);
|
|
||||||
newExtensionSettings[extension.extensionId] = newExtensionConfig;
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
addEventHook("onMessage");
|
// Load/Save extension config from/to settings
|
||||||
addEventHook("onError");
|
eventMgr.onLoadSettings = function() {
|
||||||
addEventHook("onOfflineChanged");
|
logger.log("onLoadSettings");
|
||||||
addEventHook("onUserActive");
|
_.each(extensionList, function(extension) {
|
||||||
addEventHook("onAsyncRunning");
|
var isChecked = !extension.isOptional || extension.config.enabled === undefined || extension.config.enabled === true;
|
||||||
addEventHook("onPeriodicRun");
|
utils.setInputChecked("#input-enable-extension-" + extension.extensionId, isChecked);
|
||||||
|
// Special case for Markdown Extra and MathJax
|
||||||
|
if(extension.extensionId == 'markdownExtra') {
|
||||||
|
utils.setInputChecked("#input-settings-markdown-extra", isChecked);
|
||||||
|
}
|
||||||
|
else if(extension.extensionId == 'mathJax') {
|
||||||
|
utils.setInputChecked("#input-settings-mathjax", isChecked);
|
||||||
|
}
|
||||||
|
var onLoadSettingsListener = extension.onLoadSettings;
|
||||||
|
onLoadSettingsListener && onLoadSettingsListener();
|
||||||
|
});
|
||||||
|
};
|
||||||
|
eventMgr.onSaveSettings = function(newExtensionSettings, event) {
|
||||||
|
logger.log("onSaveSettings");
|
||||||
|
_.each(extensionList, function(extension) {
|
||||||
|
var newExtensionConfig = _.extend({}, extension.defaultConfig);
|
||||||
|
newExtensionConfig.enabled = utils.getInputChecked("#input-enable-extension-" + extension.extensionId);
|
||||||
|
var isChecked;
|
||||||
|
// Special case for Markdown Extra and MathJax
|
||||||
|
if(extension.extensionId == 'markdownExtra') {
|
||||||
|
isChecked = utils.getInputChecked("#input-settings-markdown-extra");
|
||||||
|
if(isChecked != extension.enabled) {
|
||||||
|
newExtensionConfig.enabled = isChecked;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if(extension.extensionId == 'mathJax') {
|
||||||
|
isChecked = utils.getInputChecked("#input-settings-mathjax");
|
||||||
|
if(isChecked != extension.enabled) {
|
||||||
|
newExtensionConfig.enabled = isChecked;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var onSaveSettingsListener = extension.onSaveSettings;
|
||||||
|
onSaveSettingsListener && onSaveSettingsListener(newExtensionConfig, event);
|
||||||
|
newExtensionSettings[extension.extensionId] = newExtensionConfig;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
// To access modules that are loaded after extensions
|
addEventHook("onMessage");
|
||||||
addEventHook("onEditorCreated");
|
addEventHook("onError");
|
||||||
addEventHook("onFileMgrCreated");
|
addEventHook("onOfflineChanged");
|
||||||
addEventHook("onSynchronizerCreated");
|
addEventHook("onUserActive");
|
||||||
addEventHook("onPublisherCreated");
|
addEventHook("onAsyncRunning");
|
||||||
addEventHook("onEventMgrCreated");
|
addEventHook("onPeriodicRun");
|
||||||
|
|
||||||
// Operations on files
|
// To access modules that are loaded after extensions
|
||||||
addEventHook("onFileCreated");
|
addEventHook("onEditorCreated");
|
||||||
addEventHook("onFileDeleted");
|
addEventHook("onFileMgrCreated");
|
||||||
addEventHook("onFileSelected");
|
addEventHook("onSynchronizerCreated");
|
||||||
addEventHook("onFileOpen");
|
addEventHook("onPublisherCreated");
|
||||||
addEventHook("onFileClosed");
|
addEventHook("onEventMgrCreated");
|
||||||
addEventHook("onContentChanged");
|
|
||||||
addEventHook("onTitleChanged");
|
|
||||||
|
|
||||||
// Operations on folders
|
// Operations on files
|
||||||
addEventHook("onFoldersChanged");
|
addEventHook("onFileCreated");
|
||||||
|
addEventHook("onFileDeleted");
|
||||||
|
addEventHook("onFileSelected");
|
||||||
|
addEventHook("onFileOpen");
|
||||||
|
addEventHook("onFileClosed");
|
||||||
|
addEventHook("onContentChanged");
|
||||||
|
addEventHook("onTitleChanged");
|
||||||
|
|
||||||
// Sync events
|
// Operations on folders
|
||||||
addEventHook("onSyncRunning");
|
addEventHook("onFoldersChanged");
|
||||||
addEventHook("onSyncSuccess");
|
|
||||||
addEventHook("onSyncImportSuccess");
|
|
||||||
addEventHook("onSyncExportSuccess");
|
|
||||||
addEventHook("onSyncRemoved");
|
|
||||||
|
|
||||||
// Publish events
|
// Sync events
|
||||||
addEventHook("onPublishRunning");
|
addEventHook("onSyncRunning");
|
||||||
addEventHook("onPublishSuccess");
|
addEventHook("onSyncSuccess");
|
||||||
addEventHook("onNewPublishSuccess");
|
addEventHook("onSyncImportSuccess");
|
||||||
addEventHook("onPublishRemoved");
|
addEventHook("onSyncExportSuccess");
|
||||||
|
addEventHook("onSyncRemoved");
|
||||||
|
|
||||||
// Operations on Layout
|
// Publish events
|
||||||
addEventHook("onLayoutCreated");
|
addEventHook("onPublishRunning");
|
||||||
addEventHook("onLayoutResize");
|
addEventHook("onPublishSuccess");
|
||||||
addEventHook("onExtensionButtonResize");
|
addEventHook("onNewPublishSuccess");
|
||||||
|
addEventHook("onPublishRemoved");
|
||||||
|
|
||||||
// Operations on editor
|
// Operations on Layout
|
||||||
addEventHook("onPagedownConfigure");
|
addEventHook("onLayoutCreated");
|
||||||
addEventHook("onSectionsCreated");
|
addEventHook("onLayoutResize");
|
||||||
addEventHook("onCursorCoordinates");
|
addEventHook("onExtensionButtonResize");
|
||||||
|
|
||||||
// Operations on comments
|
// Operations on editor
|
||||||
addEventHook("onDiscussionCreated");
|
addEventHook("onPagedownConfigure");
|
||||||
addEventHook("onDiscussionRemoved");
|
addEventHook("onSectionsCreated");
|
||||||
addEventHook("onCommentsChanged");
|
addEventHook("onCursorCoordinates");
|
||||||
|
addEventHook("onEditorPopover");
|
||||||
|
|
||||||
// Refresh twitter buttons
|
// Operations on comments
|
||||||
addEventHook("onTweet");
|
addEventHook("onDiscussionCreated");
|
||||||
|
addEventHook("onDiscussionRemoved");
|
||||||
|
addEventHook("onCommentsChanged");
|
||||||
|
|
||||||
|
// Refresh twitter buttons
|
||||||
|
addEventHook("onTweet");
|
||||||
|
|
||||||
|
|
||||||
var onPreviewFinished = createEventHook("onPreviewFinished");
|
var onPreviewFinished = createEventHook("onPreviewFinished");
|
||||||
var onAsyncPreviewListenerList = getExtensionListenerList("onAsyncPreview");
|
var onAsyncPreviewListenerList = getExtensionListenerList("onAsyncPreview");
|
||||||
var previewContentsElt;
|
var previewContentsElt;
|
||||||
var $previewContentsElt;
|
var $previewContentsElt;
|
||||||
eventMgr.onAsyncPreview = function() {
|
eventMgr.onAsyncPreview = function() {
|
||||||
logger.log("onAsyncPreview");
|
logger.log("onAsyncPreview");
|
||||||
function recursiveCall(callbackList) {
|
function recursiveCall(callbackList) {
|
||||||
var callback = callbackList.length ? callbackList.shift() : function() {
|
var callback = callbackList.length ? callbackList.shift() : function() {
|
||||||
setTimeout(function() {
|
setTimeout(function() {
|
||||||
var html = "";
|
var html = "";
|
||||||
_.each(previewContentsElt.children, function(elt) {
|
_.each(previewContentsElt.children, function(elt) {
|
||||||
html += elt.innerHTML;
|
html += elt.innerHTML;
|
||||||
});
|
});
|
||||||
var htmlWithComments = utils.trim(html);
|
var htmlWithComments = utils.trim(html);
|
||||||
var htmlWithoutComments = htmlWithComments.replace(/ <span class="comment label label-danger">.*?<\/span> /g, '');
|
var htmlWithoutComments = htmlWithComments.replace(/ <span class="comment label label-danger">.*?<\/span> /g, '');
|
||||||
onPreviewFinished(htmlWithComments, htmlWithoutComments);
|
onPreviewFinished(htmlWithComments, htmlWithoutComments);
|
||||||
}, 10);
|
}, 10);
|
||||||
};
|
};
|
||||||
callback(function() {
|
callback(function() {
|
||||||
recursiveCall(callbackList);
|
recursiveCall(callbackList);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
recursiveCall(onAsyncPreviewListenerList.concat([function(callback) {
|
|
||||||
// We assume some images are loading asynchronously after the preview
|
|
||||||
$previewContentsElt.waitForImages(callback);
|
|
||||||
}]));
|
|
||||||
};
|
|
||||||
|
|
||||||
var onReady = createEventHook("onReady");
|
recursiveCall(onAsyncPreviewListenerList.concat([
|
||||||
eventMgr.onReady = function() {
|
function(callback) {
|
||||||
previewContentsElt = document.getElementById('preview-contents');
|
// We assume some images are loading asynchronously after the preview
|
||||||
$previewContentsElt = $(previewContentsElt);
|
$previewContentsElt.waitForImages(callback);
|
||||||
|
}
|
||||||
|
]));
|
||||||
|
};
|
||||||
|
|
||||||
// Create a button from an extension listener
|
var onReady = createEventHook("onReady");
|
||||||
var createBtn = function(listener) {
|
eventMgr.onReady = function() {
|
||||||
var buttonGrpElt = crel('div', {
|
previewContentsElt = document.getElementById('preview-contents');
|
||||||
class: 'btn-group'
|
$previewContentsElt = $(previewContentsElt);
|
||||||
});
|
|
||||||
var btnElt = listener();
|
|
||||||
if(_.isString(btnElt)) {
|
|
||||||
buttonGrpElt.innerHTML = btnElt;
|
|
||||||
}
|
|
||||||
else if(_.isElement(btnElt)) {
|
|
||||||
buttonGrpElt.appendChild(btnElt);
|
|
||||||
}
|
|
||||||
return buttonGrpElt;
|
|
||||||
};
|
|
||||||
|
|
||||||
if(window.viewerMode === false) {
|
// Create a button from an extension listener
|
||||||
// Create accordion in settings dialog
|
var createBtn = function(listener) {
|
||||||
var accordionHtml = _.chain(extensionList).sortBy(function(extension) {
|
var buttonGrpElt = crel('div', {
|
||||||
return extension.extensionName.toLowerCase();
|
class: 'btn-group'
|
||||||
}).reduce(function(html, extension) {
|
});
|
||||||
return html + (extension.settingsBlock ? _.template(settingsExtensionsAccordionHTML, {
|
var btnElt = listener();
|
||||||
extensionId: extension.extensionId,
|
if(_.isString(btnElt)) {
|
||||||
extensionName: extension.extensionName,
|
buttonGrpElt.innerHTML = btnElt;
|
||||||
isOptional: extension.isOptional,
|
}
|
||||||
settingsBlock: extension.settingsBlock
|
else if(_.isElement(btnElt)) {
|
||||||
}) : "");
|
buttonGrpElt.appendChild(btnElt);
|
||||||
}, "").value();
|
}
|
||||||
document.querySelector('.accordion-extensions').innerHTML = accordionHtml;
|
return buttonGrpElt;
|
||||||
|
};
|
||||||
|
|
||||||
// Create extension buttons
|
if(window.viewerMode === false) {
|
||||||
logger.log("onCreateButton");
|
// Create accordion in settings dialog
|
||||||
var onCreateButtonListenerList = getExtensionListenerList("onCreateButton");
|
var accordionHtml = _.chain(extensionList).sortBy(function(extension) {
|
||||||
var extensionButtonsFragment = document.createDocumentFragment();
|
return extension.extensionName.toLowerCase();
|
||||||
_.each(onCreateButtonListenerList, function(listener) {
|
}).reduce(function(html, extension) {
|
||||||
extensionButtonsFragment.appendChild(createBtn(listener));
|
return html + (extension.settingsBlock ? _.template(settingsExtensionsAccordionHTML, {
|
||||||
});
|
extensionId: extension.extensionId,
|
||||||
document.querySelector('.extension-buttons').appendChild(extensionButtonsFragment);
|
extensionName: extension.extensionName,
|
||||||
}
|
isOptional: extension.isOptional,
|
||||||
|
settingsBlock: extension.settingsBlock
|
||||||
|
}) : "");
|
||||||
|
}, "").value();
|
||||||
|
document.querySelector('.accordion-extensions').innerHTML = accordionHtml;
|
||||||
|
|
||||||
// Create extension preview buttons
|
// Create extension buttons
|
||||||
logger.log("onCreatePreviewButton");
|
logger.log("onCreateButton");
|
||||||
var onCreatePreviewButtonListenerList = getExtensionListenerList("onCreatePreviewButton");
|
var onCreateButtonListenerList = getExtensionListenerList("onCreateButton");
|
||||||
var extensionPreviewButtonsFragment = document.createDocumentFragment();
|
var extensionButtonsFragment = document.createDocumentFragment();
|
||||||
_.each(onCreatePreviewButtonListenerList, function(listener) {
|
_.each(onCreateButtonListenerList, function(listener) {
|
||||||
extensionPreviewButtonsFragment.appendChild(createBtn(listener));
|
extensionButtonsFragment.appendChild(createBtn(listener));
|
||||||
});
|
});
|
||||||
var previewButtonsElt = document.querySelector('.extension-preview-buttons');
|
document.querySelector('.extension-buttons').appendChild(extensionButtonsFragment);
|
||||||
previewButtonsElt.appendChild(extensionPreviewButtonsFragment);
|
}
|
||||||
|
|
||||||
// Call onReady listeners
|
// Create extension preview buttons
|
||||||
onReady();
|
logger.log("onCreatePreviewButton");
|
||||||
};
|
var onCreatePreviewButtonListenerList = getExtensionListenerList("onCreatePreviewButton");
|
||||||
|
var extensionPreviewButtonsFragment = document.createDocumentFragment();
|
||||||
|
_.each(onCreatePreviewButtonListenerList, function(listener) {
|
||||||
|
extensionPreviewButtonsFragment.appendChild(createBtn(listener));
|
||||||
|
});
|
||||||
|
var previewButtonsElt = document.querySelector('.extension-preview-buttons');
|
||||||
|
previewButtonsElt.appendChild(extensionPreviewButtonsFragment);
|
||||||
|
|
||||||
// For extensions that need to call other extensions
|
// Shall close every popover
|
||||||
eventMgr.onEventMgrCreated(eventMgr);
|
mousetrap.bind('escape', function() {
|
||||||
return eventMgr;
|
eventMgr.onEditorPopover();
|
||||||
|
});
|
||||||
|
|
||||||
|
// Call onReady listeners
|
||||||
|
onReady();
|
||||||
|
};
|
||||||
|
|
||||||
|
// For extensions that need to call other extensions
|
||||||
|
eventMgr.onEventMgrCreated(eventMgr);
|
||||||
|
return eventMgr;
|
||||||
});
|
});
|
||||||
|
@ -226,6 +226,12 @@ define([
|
|||||||
currentContext && currentContext.$commentElt.popover('toggle').popover('destroy');
|
currentContext && currentContext.$commentElt.popover('toggle').popover('destroy');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
comments.onEditorPopover = function() {
|
||||||
|
closeCurrentPopover();
|
||||||
|
editor.focus();
|
||||||
|
editor.adjustCursorPosition();
|
||||||
|
};
|
||||||
|
|
||||||
comments.onDiscussionCreated = function(fileDesc) {
|
comments.onDiscussionCreated = function(fileDesc) {
|
||||||
currentFileDesc === fileDesc && refreshDiscussions();
|
currentFileDesc === fileDesc && refreshDiscussions();
|
||||||
};
|
};
|
||||||
@ -265,7 +271,7 @@ define([
|
|||||||
}
|
}
|
||||||
|
|
||||||
comments.onReady = function() {
|
comments.onReady = function() {
|
||||||
cssApplier = rangy.createCssClassApplier("comment-highlight", {
|
cssApplier = rangy.createCssClassApplier('comment-highlight', {
|
||||||
normalize: false
|
normalize: false
|
||||||
});
|
});
|
||||||
var previousContent = '';
|
var previousContent = '';
|
||||||
@ -308,7 +314,7 @@ define([
|
|||||||
selector: '#wmd-input > .editor-margin > .discussion'
|
selector: '#wmd-input > .editor-margin > .discussion'
|
||||||
});
|
});
|
||||||
$(marginElt).on('show.bs.popover', function(evt) {
|
$(marginElt).on('show.bs.popover', function(evt) {
|
||||||
closeCurrentPopover();
|
eventMgr.onEditorPopover();
|
||||||
var context = new Context(evt.target, currentFileDesc);
|
var context = new Context(evt.target, currentFileDesc);
|
||||||
currentContext = context;
|
currentContext = context;
|
||||||
|
|
||||||
@ -348,18 +354,10 @@ define([
|
|||||||
|
|
||||||
var $addButton = $(popoverElt.querySelector('.action-add-comment'));
|
var $addButton = $(popoverElt.querySelector('.action-add-comment'));
|
||||||
$().add(context.$contentInputElt).add(context.$authorInputElt).keydown(function(evt) {
|
$().add(context.$contentInputElt).add(context.$authorInputElt).keydown(function(evt) {
|
||||||
// Enter key
|
if(evt.which === 13) {
|
||||||
switch(evt.which) {
|
// Enter key
|
||||||
case 13:
|
evt.preventDefault();
|
||||||
evt.preventDefault();
|
$addButton.click();
|
||||||
$addButton.click();
|
|
||||||
return;
|
|
||||||
case 27:
|
|
||||||
evt.preventDefault();
|
|
||||||
closeCurrentPopover();
|
|
||||||
editor.focus();
|
|
||||||
editor.adjustCursorPosition();
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
$addButton.click(function(evt) {
|
$addButton.click(function(evt) {
|
||||||
@ -418,7 +416,7 @@ define([
|
|||||||
context.rangyRange = rangy.createRange();
|
context.rangyRange = rangy.createRange();
|
||||||
context.rangyRange.setStart(context.selectionRange.startContainer, context.selectionRange.startOffset);
|
context.rangyRange.setStart(context.selectionRange.startContainer, context.selectionRange.startOffset);
|
||||||
context.rangyRange.setEnd(context.selectionRange.endContainer, context.selectionRange.endOffset);
|
context.rangyRange.setEnd(context.selectionRange.endContainer, context.selectionRange.endOffset);
|
||||||
setTimeout(function() { // Need to delay this because it's not refreshed properly
|
setTimeout(function() { // Delay this because not refreshed properly
|
||||||
if(currentContext === context) {
|
if(currentContext === context) {
|
||||||
cssApplier.applyToRange(context.rangyRange);
|
cssApplier.applyToRange(context.rangyRange);
|
||||||
}
|
}
|
||||||
@ -452,7 +450,6 @@ define([
|
|||||||
evt.stopPropagation();
|
evt.stopPropagation();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
var $newCommentElt = $(newCommentElt);
|
var $newCommentElt = $(newCommentElt);
|
||||||
$openDiscussionElt = $('.button-open-discussion').click(function(evt) {
|
$openDiscussionElt = $('.button-open-discussion').click(function(evt) {
|
||||||
var $commentElt = $newCommentElt;
|
var $commentElt = $newCommentElt;
|
||||||
|
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