Editor: add global-replace/replaceAll commands
* Collateral fix: correctly restore openDialog() after Esc * refactor html templates
This commit is contained in:
parent
b4eaac4ef9
commit
20141b7bfa
|
@ -100,6 +100,18 @@
|
|||
"message": "Theme",
|
||||
"description": "Label for the style editor's CSS theme."
|
||||
},
|
||||
"confirmNo": {
|
||||
"message": "No",
|
||||
"description": "'No' button in a confirm dialog"
|
||||
},
|
||||
"confirmStop": {
|
||||
"message": "Stop",
|
||||
"description": "'Stop' button in a confirm dialog"
|
||||
},
|
||||
"confirmYes": {
|
||||
"message": "Yes",
|
||||
"description": "'Yes' button in a confirm dialog"
|
||||
},
|
||||
"dbError": {
|
||||
"message": "An error has occurred using the Stylish database. Would you like to visit a web page with possible solutions?",
|
||||
"description": "Prompt when a DB error is encountered"
|
||||
|
@ -229,6 +241,18 @@
|
|||
"message": "Show number of styles active for the current site on the toolbar button",
|
||||
"description": "Label for the checkbox controlling toolbar badge text."
|
||||
},
|
||||
"replace": {
|
||||
"message": "Replace",
|
||||
"description": "Label before the replace input field in the editor shown on Ctrl-H"
|
||||
},
|
||||
"replaceAll": {
|
||||
"message": "Replace all",
|
||||
"description": "Label before the replace input field in the editor shown on 'replaceAll' hotkey"
|
||||
},
|
||||
"replaceWith": {
|
||||
"message": "Replace with",
|
||||
"description": "Label before the replace-with input field in the editor shown on Ctrl-H etc."
|
||||
},
|
||||
"search": {
|
||||
"message": "Search",
|
||||
"description": "Label before the search input field in the editor shown on Ctrl-F"
|
||||
|
|
|
@ -160,6 +160,15 @@
|
|||
outline: -webkit-focus-ring-color auto 5px;
|
||||
outline-offset: -2px;
|
||||
}
|
||||
.CodeMirror-search-field {
|
||||
width: 10em;
|
||||
}
|
||||
.CodeMirror-jump-field {
|
||||
width: 5em;
|
||||
}
|
||||
.CodeMirror-search-hint {
|
||||
color: #888;
|
||||
}
|
||||
@-webkit-keyframes highlight {
|
||||
from {
|
||||
background-color: #ff9;
|
||||
|
|
211
edit.js
211
edit.js
|
@ -10,8 +10,8 @@ var useHistoryBack; // use browser history back when "back to manage" is click
|
|||
var propertyToCss = {urls: "url", urlPrefixes: "url-prefix", domains: "domain", regexps: "regexp"};
|
||||
var CssToProperty = {"url": "urls", "url-prefix": "urlPrefixes", "domain": "domains", "regexp": "regexps"};
|
||||
|
||||
// templates
|
||||
var appliesToTemplate = tHTML('\
|
||||
var template = {
|
||||
appliesTo: '\
|
||||
<li>\
|
||||
<select name="applies-type" class="applies-type style-contributor">\
|
||||
<option value="url" i18n-text="appliesUrlOption"></option>\
|
||||
|
@ -23,15 +23,13 @@ var appliesToTemplate = tHTML('\
|
|||
<button class="remove-applies-to" i18n-text="appliesRemove"></button>\
|
||||
<button class="add-applies-to" i18n-text="appliesAdd"></button>\
|
||||
</li>\
|
||||
');
|
||||
|
||||
var appliesToEverythingTemplate = tHTML('\
|
||||
',
|
||||
appliesToEverything: '\
|
||||
<li class="applies-to-everything" i18n-html="appliesToEverything")>\
|
||||
<button class="add-applies-to" i18n-text="appliesSpecify"></button>\
|
||||
</li>\
|
||||
');
|
||||
|
||||
var sectionTemplate = tHTML('\
|
||||
',
|
||||
section: '\
|
||||
<div>\
|
||||
<label i18n-text="sectionCode"></label>\
|
||||
<textarea class="code"></textarea>\
|
||||
|
@ -46,12 +44,44 @@ var sectionTemplate = tHTML('\
|
|||
<button class="add-section" i18n-text="sectionAdd"></button>\
|
||||
<button class="beautify-section" i18n-text="styleBeautify"></button>\
|
||||
</div>\
|
||||
');
|
||||
|
||||
var findTemplate = t("search") + ': <input type="text" style="width: 10em" class="CodeMirror-search-field"/> ' +
|
||||
'<span style="color: #888" class="CodeMirror-search-hint">(' + t("searchRegexp") + ')</span>';
|
||||
|
||||
var jumpToLineTemplate = t('editGotoLine') + ': <input class="CodeMirror-jump-field" type="text" style="width: 5em"/>';
|
||||
',
|
||||
find: '\
|
||||
<span i18n-text="search">: \
|
||||
<input type="text" class="CodeMirror-search-field"/> \
|
||||
<span class="CodeMirror-search-hint">(<span i18n-text="searchRegexp"></span>)</span>\
|
||||
</span>\
|
||||
',
|
||||
replace: '\
|
||||
<span i18n-text="replace">: \
|
||||
<input type="text" class="CodeMirror-search-field"/> \
|
||||
<span class="CodeMirror-search-hint">(<span i18n-text="searchRegexp"></span>)</span>\
|
||||
</span>\
|
||||
',
|
||||
replaceAll: '\
|
||||
<span i18n-text="replaceAll">: \
|
||||
<input type="text" class="CodeMirror-search-field"/> \
|
||||
<span class="CodeMirror-search-hint">(<span i18n-text="searchRegexp"></span>)</span>\
|
||||
</span>\
|
||||
',
|
||||
replaceWith: '\
|
||||
<span i18n-text="replaceWith">: \
|
||||
<input type="text" class="CodeMirror-search-field"/>\
|
||||
</span>\
|
||||
',
|
||||
replaceConfirm: '\
|
||||
<span i18n-text="replace">? \
|
||||
<button i18n-text="confirmYes"></button> \
|
||||
<button i18n-text="confirmNo"></button> \
|
||||
<button i18n-text="confirmStop"></button>\
|
||||
</span>\
|
||||
',
|
||||
jumpToLine: '\
|
||||
<span i18n-text="editGotoLine">: \
|
||||
<input class="CodeMirror-jump-field" type="text"/>\
|
||||
</span>\
|
||||
'
|
||||
}
|
||||
Object.keys(template).forEach(function(name) { template[name] = tHTML(template[name]); });
|
||||
|
||||
// make querySelectorAll enumeration code readable
|
||||
["forEach", "some", "indexOf"].forEach(function(method) {
|
||||
|
@ -61,6 +91,12 @@ var jumpToLineTemplate = t('editGotoLine') + ': <input class="CodeMirror-jump-fi
|
|||
// Chrome pre-34
|
||||
Element.prototype.matches = Element.prototype.matches || Element.prototype.webkitMatchesSelector;
|
||||
|
||||
Array.prototype.rotate = function(amount) { // negative amount == rotate left
|
||||
var r = this.slice(-amount, this.length);
|
||||
Array.prototype.push.apply(r, this.slice(0, this.length - r.length));
|
||||
return r;
|
||||
}
|
||||
|
||||
// reroute handling to nearest editor when keypress resolves to one of these commands
|
||||
var hotkeyRerouter = {
|
||||
commands: {
|
||||
|
@ -476,25 +512,25 @@ function addAppliesTo(list, name, value) {
|
|||
}
|
||||
var e;
|
||||
if (name && value) {
|
||||
e = appliesToTemplate.cloneNode(true);
|
||||
e = template.appliesTo.cloneNode(true);
|
||||
e.querySelector("[name=applies-type]").value = name;
|
||||
e.querySelector("[name=applies-value]").value = value;
|
||||
e.querySelector(".remove-applies-to").addEventListener("click", removeAppliesTo, false);
|
||||
} else if (showingEverything || list.hasChildNodes()) {
|
||||
e = appliesToTemplate.cloneNode(true);
|
||||
e = template.appliesTo.cloneNode(true);
|
||||
if (list.hasChildNodes()) {
|
||||
e.querySelector("[name=applies-type]").value = list.querySelector("li:last-child [name='applies-type']").value;
|
||||
}
|
||||
e.querySelector(".remove-applies-to").addEventListener("click", removeAppliesTo, false);
|
||||
} else {
|
||||
e = appliesToEverythingTemplate.cloneNode(true);
|
||||
e = template.appliesToEverything.cloneNode(true);
|
||||
}
|
||||
e.querySelector(".add-applies-to").addEventListener("click", function() {addAppliesTo(this.parentNode.parentNode)}, false);
|
||||
list.appendChild(e);
|
||||
}
|
||||
|
||||
function addSection(event, section) {
|
||||
var div = sectionTemplate.cloneNode(true);
|
||||
var div = template.section.cloneNode(true);
|
||||
div.querySelector(".applies-to-help").addEventListener("click", showAppliesToHelp, false);
|
||||
div.querySelector(".remove-section").addEventListener("click", removeSection, false);
|
||||
div.querySelector(".add-section").addEventListener("click", addSection, false);
|
||||
|
@ -589,8 +625,11 @@ function setupGlobalSearch() {
|
|||
var originalCommand = {
|
||||
find: CodeMirror.commands.find,
|
||||
findNext: CodeMirror.commands.findNext,
|
||||
findPrev: CodeMirror.commands.findPrev
|
||||
findPrev: CodeMirror.commands.findPrev,
|
||||
replace: CodeMirror.commands.replace
|
||||
}
|
||||
var originalOpenDialog = CodeMirror.prototype.openDialog;
|
||||
var originalOpenConfirm = CodeMirror.prototype.openConfirm;
|
||||
|
||||
var curState; // cm.state.search for last used 'find'
|
||||
|
||||
|
@ -614,33 +653,43 @@ function setupGlobalSearch() {
|
|||
return cm.state.search;
|
||||
}
|
||||
|
||||
function find(activeCM) {
|
||||
// temporarily overrides the original openDialog with the provided template's innerHTML
|
||||
function customizeOpenDialog(cm, template, callback) {
|
||||
cm.openDialog = function(tmpl, cb, opt) {
|
||||
// invoke 'callback' and bind 'this' to the original callback
|
||||
originalOpenDialog.call(cm, template.innerHTML, callback.bind(cb), opt);
|
||||
};
|
||||
setTimeout(function() { cm.openDialog = originalOpenDialog; }, 0);
|
||||
}
|
||||
|
||||
function focusClosestCM(activeCM) {
|
||||
editors.lastActive = activeCM;
|
||||
var cm = getEditorInSight();
|
||||
if (cm != activeCM) {
|
||||
cm.focus();
|
||||
activeCM = cm;
|
||||
}
|
||||
var originalOpenDialog = activeCM.openDialog;
|
||||
activeCM.openDialog = function(template, callback, options) {
|
||||
originalOpenDialog.call(activeCM, findTemplate, function(query) {
|
||||
activeCM.openDialog = originalOpenDialog;
|
||||
callback(query);
|
||||
curState = activeCM.state.search;
|
||||
if (editors.length == 1 || !curState.query) {
|
||||
return;
|
||||
return cm;
|
||||
|
||||
}
|
||||
|
||||
function find(activeCM) {
|
||||
activeCM = focusClosestCM(activeCM);
|
||||
customizeOpenDialog(activeCM, template.find, function(query) {
|
||||
this(query);
|
||||
curState = activeCM.state.search;
|
||||
if (editors.length == 1 || !curState.query) {
|
||||
return;
|
||||
}
|
||||
editors.forEach(function(cm) {
|
||||
if (cm != activeCM) {
|
||||
cm.execCommand("clearSearch");
|
||||
updateState(cm, curState);
|
||||
}
|
||||
editors.forEach(function(cm) {
|
||||
if (cm != activeCM) {
|
||||
cm.execCommand("clearSearch");
|
||||
updateState(cm, curState);
|
||||
}
|
||||
});
|
||||
if (CodeMirror.cmpPos(curState.posFrom, curState.posTo) == 0) {
|
||||
findNext(activeCM);
|
||||
}
|
||||
}, options);
|
||||
}
|
||||
});
|
||||
if (CodeMirror.cmpPos(curState.posFrom, curState.posTo) == 0) {
|
||||
findNext(activeCM);
|
||||
}
|
||||
});
|
||||
originalCommand.find(activeCM);
|
||||
}
|
||||
|
||||
|
@ -714,14 +763,90 @@ function setupGlobalSearch() {
|
|||
findNext(cm, true);
|
||||
}
|
||||
|
||||
function replace(activeCM, all) {
|
||||
var queue, query, replacement;
|
||||
activeCM = focusClosestCM(activeCM);
|
||||
customizeOpenDialog(activeCM, template[all ? "replaceAll" : "replace"], function(txt) {
|
||||
query = txt;
|
||||
customizeOpenDialog(activeCM, template.replaceWith, function(txt) {
|
||||
replacement = txt;
|
||||
queue = editors.rotate(-editors.indexOf(activeCM));
|
||||
all ? editors.forEach(doReplace) : doReplace();
|
||||
});
|
||||
this(query);
|
||||
});
|
||||
originalCommand.replace(activeCM, all);
|
||||
|
||||
function doReplace() {
|
||||
var cm = queue.shift();
|
||||
if (!cm) {
|
||||
if (!all) {
|
||||
editors.lastActive.focus();
|
||||
}
|
||||
return;
|
||||
}
|
||||
// hide the first two dialogs (replace, replaceWith)
|
||||
cm.openDialog = function(tmpl, callback, opt) {
|
||||
cm.openDialog = function(tmpl, callback, opt) {
|
||||
cm.openDialog = originalOpenDialog;
|
||||
if (all) {
|
||||
callback(replacement);
|
||||
} else {
|
||||
doConfirm(cm);
|
||||
callback(replacement);
|
||||
if (!cm.getWrapperElement().querySelector(".CodeMirror-dialog")) {
|
||||
// no dialog == nothing found in the current CM, move to the next
|
||||
doReplace();
|
||||
}
|
||||
}
|
||||
};
|
||||
callback(query);
|
||||
};
|
||||
originalCommand.replace(cm, all);
|
||||
}
|
||||
function doConfirm(cm) {
|
||||
var wrapAround = false;
|
||||
var origPos = cm.getCursor();
|
||||
cm.openConfirm = function overrideConfirm(tmpl, callbacks, opt) {
|
||||
var ovrCallbacks = callbacks.map(function(callback) {
|
||||
return function() {
|
||||
makeSectionVisible(cm);
|
||||
cm.openConfirm = overrideConfirm;
|
||||
setTimeout(function() { cm.openConfirm = originalOpenConfirm; }, 0);
|
||||
|
||||
var pos = cm.getCursor();
|
||||
callback();
|
||||
var cmp = CodeMirror.cmpPos(cm.getCursor(), pos);
|
||||
wrapAround |= cmp <= 0;
|
||||
|
||||
var dlg = cm.getWrapperElement().querySelector(".CodeMirror-dialog");
|
||||
if (!dlg || cmp == 0 || wrapAround && CodeMirror.cmpPos(cm.getCursor(), origPos) >= 0) {
|
||||
if (dlg) {
|
||||
dlg.remove();
|
||||
}
|
||||
doReplace();
|
||||
}
|
||||
}
|
||||
});
|
||||
originalOpenConfirm.call(cm, template.replaceConfirm.innerHTML, ovrCallbacks, opt);
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
function replaceAll(cm) {
|
||||
replace(cm, true);
|
||||
}
|
||||
|
||||
CodeMirror.commands.find = find;
|
||||
CodeMirror.commands.findNext = findNext;
|
||||
CodeMirror.commands.findPrev = findPrev;
|
||||
CodeMirror.commands.replace = replace;
|
||||
CodeMirror.commands.replaceAll = replaceAll;
|
||||
}
|
||||
|
||||
function jumpToLine(cm) {
|
||||
var cur = cm.getCursor();
|
||||
cm.openDialog(jumpToLineTemplate, function(str) {
|
||||
cm.openDialog(template.jumpToLine.innerHTML, function(str) {
|
||||
var m = str.match(/^\s*(\d+)(?:\s*:\s*(\d+))?\s*$/);
|
||||
if (m) {
|
||||
cm.setCursor(m[1] - 1, m[2] ? m[2] - 1 : cur.ch);
|
||||
|
@ -1087,7 +1212,7 @@ function validate() {
|
|||
// validate the regexps
|
||||
if (document.querySelectorAll(".applies-to-list").some(function(list) {
|
||||
return list.childNodes.some(function(li) {
|
||||
if (li.className == appliesToEverythingTemplate.className) {
|
||||
if (li.className == template.appliesToEverything.className) {
|
||||
return false;
|
||||
}
|
||||
var valueElement = li.querySelector("[name=applies-value]");
|
||||
|
@ -1153,7 +1278,7 @@ function getSections() {
|
|||
function getMeta(e) {
|
||||
var meta = {};
|
||||
e.querySelector(".applies-to-list").childNodes.forEach(function(li) {
|
||||
if (li.className == appliesToEverythingTemplate.className) {
|
||||
if (li.className == template.appliesToEverything.className) {
|
||||
return;
|
||||
}
|
||||
var type = li.querySelector("[name=applies-type]").value;
|
||||
|
|
Loading…
Reference in New Issue
Block a user