fixes for global search/replace
* Enter key in input fields: * [Find mode] closes the dialog and focuses the matched text * [Replace mode] performs a single replace action * Undo button checks the new CM changeGeneration and disables itself if the changes were already undone * doUndo focuses the visible undo button
This commit is contained in:
parent
671b276532
commit
b2100ea853
|
@ -47,7 +47,6 @@ onDOMready().then(() => {
|
||||||
tally: null,
|
tally: null,
|
||||||
originalFocus: null,
|
originalFocus: null,
|
||||||
|
|
||||||
undo: null,
|
|
||||||
undoHistory: [],
|
undoHistory: [],
|
||||||
|
|
||||||
searchInApplies: !document.documentElement.classList.contains('usercss'),
|
searchInApplies: !document.documentElement.classList.contains('usercss'),
|
||||||
|
@ -59,11 +58,19 @@ onDOMready().then(() => {
|
||||||
const ACTIONS = {
|
const ACTIONS = {
|
||||||
key: {
|
key: {
|
||||||
'Enter': event => {
|
'Enter': event => {
|
||||||
if (event.target.closest(focusAccessibility.ELEMENTS.join(','))) {
|
switch (document.activeElement) {
|
||||||
return false;
|
case state.input:
|
||||||
}
|
if (state.dialog.dataset.type === 'find') {
|
||||||
destroyDialog();
|
|
||||||
doSearch({canAdvance: false});
|
doSearch({canAdvance: false});
|
||||||
|
destroyDialog();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// fallthrough
|
||||||
|
case state.input2:
|
||||||
|
doReplace();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
return !event.target.closest(focusAccessibility.ELEMENTS.join(','));
|
||||||
},
|
},
|
||||||
'Esc': () => {
|
'Esc': () => {
|
||||||
destroyDialog({restoreFocus: true});
|
destroyDialog({restoreFocus: true});
|
||||||
|
@ -109,6 +116,19 @@ onDOMready().then(() => {
|
||||||
if (action && action.call(el, event) !== false) {
|
if (action && action.call(el, event) !== false) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
onfocusout() {
|
||||||
|
if (!state.dialog.contains(document.activeElement)) {
|
||||||
|
state.dialog.addEventListener('focusin', EVENTS.onfocusin);
|
||||||
|
state.dialog.removeEventListener('focusout', EVENTS.onfocusout);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onfocusin() {
|
||||||
|
state.dialog.addEventListener('focusout', EVENTS.onfocusout);
|
||||||
|
state.dialog.removeEventListener('focusin', EVENTS.onfocusin);
|
||||||
|
trimUndoHistory();
|
||||||
|
enableUndoButton(state.undoHistory.length);
|
||||||
|
if (state.find) doSearch({canAdvance: false});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -298,6 +318,7 @@ onDOMready().then(() => {
|
||||||
function doReplace() {
|
function doReplace() {
|
||||||
initState({initReplace: true});
|
initState({initReplace: true});
|
||||||
const cm = state.cmStart;
|
const cm = state.cmStart;
|
||||||
|
const generation = cm.changeGeneration();
|
||||||
const pos = getContinuationPos({cm, reverse: true});
|
const pos = getContinuationPos({cm, reverse: true});
|
||||||
const cursor = doReplaceInEditor({cm, pos});
|
const cursor = doReplaceInEditor({cm, pos});
|
||||||
if (!cursor) {
|
if (!cursor) {
|
||||||
|
@ -315,7 +336,7 @@ onDOMready().then(() => {
|
||||||
if (cm.curOp) cm.endOperation();
|
if (cm.curOp) cm.endOperation();
|
||||||
|
|
||||||
if (cursor) {
|
if (cursor) {
|
||||||
state.undoHistory.push([cm]);
|
state.undoHistory.push([[cm, generation]]);
|
||||||
enableUndoButton(true);
|
enableUndoButton(true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -324,10 +345,11 @@ onDOMready().then(() => {
|
||||||
function doReplaceAll() {
|
function doReplaceAll() {
|
||||||
initState({initReplace: true});
|
initState({initReplace: true});
|
||||||
clearMarker();
|
clearMarker();
|
||||||
|
const generations = new Map(editors.map(cm => [cm, cm.changeGeneration()]));
|
||||||
const found = editors.filter(cm => doReplaceInEditor({cm, all: true}));
|
const found = editors.filter(cm => doReplaceInEditor({cm, all: true}));
|
||||||
if (found.length) {
|
if (found.length) {
|
||||||
state.lastFind = null;
|
state.lastFind = null;
|
||||||
state.undoHistory.push(found);
|
state.undoHistory.push(found.map(cm => [cm, generations.get(cm)]));
|
||||||
enableUndoButton(true);
|
enableUndoButton(true);
|
||||||
doSearch({canAdvance: false});
|
doSearch({canAdvance: false});
|
||||||
}
|
}
|
||||||
|
@ -368,8 +390,8 @@ onDOMready().then(() => {
|
||||||
function doUndo() {
|
function doUndo() {
|
||||||
let undoneSome;
|
let undoneSome;
|
||||||
saveWindowScrollPos();
|
saveWindowScrollPos();
|
||||||
for (const cm of state.undoHistory.pop() || []) {
|
for (const [cm, generation] of state.undoHistory.pop() || []) {
|
||||||
if (document.body.contains(cm.display.wrapper) && !cm.isClean()) {
|
if (document.body.contains(cm.display.wrapper) && !cm.isClean(generation)) {
|
||||||
cm.undo();
|
cm.undo();
|
||||||
cm.getAllMarks().forEach(marker =>
|
cm.getAllMarks().forEach(marker =>
|
||||||
marker !== state.marker &&
|
marker !== state.marker &&
|
||||||
|
@ -379,7 +401,11 @@ onDOMready().then(() => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
enableUndoButton(state.undoHistory.length);
|
enableUndoButton(state.undoHistory.length);
|
||||||
(state.undoHistory.length ? state.undo : state.input).focus();
|
if (state.undoHistory.length) {
|
||||||
|
focusUndoButton();
|
||||||
|
} else {
|
||||||
|
state.input.focus();
|
||||||
|
}
|
||||||
if (undoneSome) {
|
if (undoneSome) {
|
||||||
state.lastFind = null;
|
state.lastFind = null;
|
||||||
restoreWindowScrollPos();
|
restoreWindowScrollPos();
|
||||||
|
@ -529,6 +555,7 @@ onDOMready().then(() => {
|
||||||
|
|
||||||
const dialog = state.dialog = template.searchReplaceDialog.cloneNode(true);
|
const dialog = state.dialog = template.searchReplaceDialog.cloneNode(true);
|
||||||
Object.assign(dialog, DIALOG_PROPS.dialog);
|
Object.assign(dialog, DIALOG_PROPS.dialog);
|
||||||
|
dialog.addEventListener('focusout', EVENTS.onfocusout);
|
||||||
dialog.dataset.type = type;
|
dialog.dataset.type = type;
|
||||||
|
|
||||||
const content = $('[data-type="content"]', dialog);
|
const content = $('[data-type="content"]', dialog);
|
||||||
|
@ -646,6 +673,7 @@ onDOMready().then(() => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
function enableUndoButton(enabled) {
|
function enableUndoButton(enabled) {
|
||||||
if (state.dialog && state.dialog.dataset.type === 'replace') {
|
if (state.dialog && state.dialog.dataset.type === 'replace') {
|
||||||
for (const el of $$('[data-action="undo"]', state.dialog)) {
|
for (const el of $$('[data-action="undo"]', state.dialog)) {
|
||||||
|
@ -654,6 +682,16 @@ onDOMready().then(() => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function focusUndoButton() {
|
||||||
|
for (const btn of $$('[data-action="undo"]', state.dialog)) {
|
||||||
|
if (getComputedStyle(btn).display !== 'none') {
|
||||||
|
btn.focus();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
//endregion
|
//endregion
|
||||||
//region Utility
|
//region Utility
|
||||||
|
|
||||||
|
@ -787,6 +825,20 @@ onDOMready().then(() => {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function trimUndoHistory() {
|
||||||
|
const history = state.undoHistory;
|
||||||
|
for (let last; (last = history[history.length - 1]);) {
|
||||||
|
const undoables = last.filter(([cm, generation]) =>
|
||||||
|
document.body.contains(cm.display.wrapper) && !cm.isClean(generation));
|
||||||
|
if (undoables.length) {
|
||||||
|
history[history.length - 1] = undoables;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
history.length--;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
function focusNoScroll(el) {
|
function focusNoScroll(el) {
|
||||||
if (el) {
|
if (el) {
|
||||||
saveWindowScrollPos();
|
saveWindowScrollPos();
|
||||||
|
|
Loading…
Reference in New Issue
Block a user