Source: xxe/part/SearchReplace.js

/**
 * {@link XMLEditor} part used to search/replace text.
 */
class SearchReplace extends HTMLElement {
    constructor() {
        super();

        this._options = "i"; // Default options. "i" = ignore case.
        this._xmlEditor = null;
    }

    // -----------------------------------------------------------------------
    // Custom element
    // -----------------------------------------------------------------------

    connectedCallback() {
        if (this.firstChild === null) {
            this.appendChild(SearchReplace.TEMPLATE.content.cloneNode(true));
            
            this._searchedText = this.firstElementChild;
            this._searchedText.onkeydown = this.onKeydownSearched.bind(this);
            
            this._previousButton = this._searchedText.nextElementSibling;
            this._previousButton.textContent = XUI.StockIcon["up-open"];
            this._previousButton.onclick = this.findPreviousAction.bind(this);
            
            this._nextButton = this._previousButton.nextElementSibling;
            this._nextButton.textContent = XUI.StockIcon["down-open"];
            this._nextButton.onclick = this.findNextAction.bind(this);
            
            this._replacementText = this._nextButton.nextElementSibling;
            this._replacementText.onkeydown =
                this.onKeydownReplacement.bind(this);
            
            this._replaceButton = this._replacementText.nextElementSibling;
            this._replaceButton.textContent = XUI.StockIcon["replace"];
            this._replaceButton.onclick = this.replaceNextAction.bind(this);
            
            this._replaceAllButton = this._replaceButton.nextElementSibling;
            this._replaceAllButton.textContent = XUI.StockIcon["replace-all"];
            this._replaceAllButton.onclick = this.replaceAllAction.bind(this);

            this._settingsButton = this._replaceAllButton.nextElementSibling;
            this._settingsButton.textContent = XUI.StockIcon["settings"];
            this._settingsButton.onclick = this._settingsButton.oncontextmenu =
                this.setOptionsAction.bind(this);

            this._settingsMenu = null;
            
            let id = XUI.Util.uid();
            this._searchHistory = XUI.Util.appendDatalist(id, [], this);
            this._searchedText.setAttribute("list", id);
            
            id = XUI.Util.uid();
            this._replaceHistory = XUI.Util.appendDatalist(id, [], this);
            this._replacementText.setAttribute("list", id);
        }
        // Otherwise, already connected.
    }
    
    // -----------------------------------------------------------------------
    // Event handlers
    // -----------------------------------------------------------------------

    onKeydownSearched(event) {
        if (event.key === "Enter") {
            XUI.Util.consumeEvent(event);
            this.findAction(/*previous*/ false);
        }
    }
    
    findPreviousAction(event) {
        XUI.Util.consumeEvent(event);
        if (!event.target.classList.contains("xui-control-disabled")) {
            this.findAction(true);
        }
    }
    
    findNextAction(event) {
        XUI.Util.consumeEvent(event);
        if (!event.target.classList.contains("xui-control-disabled")) {
            this.findAction(false);
        }
    }
    
    findAction(previous) {
        const searched = this._searchedText.value; // Do not trim.
        if (searched.length === 0) {
            return;
        }
        if (searched.trim().length > 0) {
            XUI.Util.prependDatalistItem(this._searchHistory, searched);
        }
        
        const op = previous? "p" : "n";
        this._xmlEditor.documentView.executeCommand(
            EXECUTE_NORMAL, "textSearchReplace",
            `${op}[${this._options}]${searched}`);
    }
    
    onKeydownReplacement(event) {
        if (event.key === "Enter") {
            XUI.Util.consumeEvent(event);
            this.replaceAction(/*all*/ false);
        }
    }
    
    replaceNextAction(event) {
        XUI.Util.consumeEvent(event);
        if (!event.target.classList.contains("xui-control-disabled")) {
            this.replaceAction(false);
        }
    }
    
    replaceAllAction(event) {
        XUI.Util.consumeEvent(event);
        if (!event.target.classList.contains("xui-control-disabled")) {
            this.replaceAction(true);
        }
    }
    
    replaceAction(all) {
        const searched = this._searchedText.value; // Do not trim.
        if (searched.length === 0) {
            return;
        }
        if (searched.trim().length > 0) {
            XUI.Util.prependDatalistItem(this._searchHistory, searched);
        }
        
        const replacement = this._replacementText.value; // May be empty.
        if (replacement.trim().length > 0) {
            XUI.Util.prependDatalistItem(this._replaceHistory, replacement);
        }
        
        const op = all? "a" : "r";
        this._xmlEditor.documentView.executeCommand(
            EXECUTE_NORMAL, "textSearchReplace",
            `${op}[${this._options}]${searched}\n${replacement}`);
    }
    
    setOptionsAction(event) {
        XUI.Util.consumeEvent(event);

        if (this._settingsMenu === null) {
            this._settingsMenu = XUI.Menu.create([
                { type: "checkbox",
                  text: "Ignore Case", name: "ignoreCase" },
                { type: "checkbox",
                  text: "Whole Word", name: "wholeWord" },
                { type: "checkbox",
                  text: "Regular Expression", name: "regularExpression" },
            ]);
            
            this._settingsMenu.addEventListener("menuitemselected", (event) => {
                this.toggleOption(event.xuiMenuItem.name);
            });
        }

        let item = this._settingsMenu.getItem("ignoreCase");
        item.setSelected(this._options.indexOf('i') >= 0);
        item = this._settingsMenu.getItem("wholeWord");
        item.setSelected(this._options.indexOf('w') >= 0);
        item = this._settingsMenu.getItem("regularExpression");
        item.setSelected(this._options.indexOf('r') >= 0);
        
        this._settingsMenu.open("menu", this._settingsButton);
    }
    
    toggleOption(optionName) {
        let option;
        switch (optionName) {
        case "ignoreCase":
        case "wholeWord":
        case "regularExpression":
            option = optionName.charAt(0);
            break;
        default:
            // Should not happen.
            return;
        }
        
        let optionList = this._options.split('');
        let index = optionList.indexOf(option);
        if (index >= 0) {
            optionList.splice(index, 1);
        } else {
            optionList.push(option);
        }
        this._options = optionList.join('');
    }
    
    // -----------------------------------------------------------------------
    // Used by XMLEditor
    // -----------------------------------------------------------------------

    set xmlEditor(editor) {
        this._xmlEditor = editor;
    }

    visiblityChanged(visible) {
        this._searchedText.value = this._replacementText.value = "";
    }
    
    documentEdited(docUID, readOnlyDoc) {
        let disable = (docUID === null);
        for (let element of [ this._searchedText,
                              this._previousButton, this._nextButton ]) {
            SearchReplace.setDisabled(element, disable);
        }
        
        disable = (disable || readOnlyDoc);
        for (let element of [ this._replacementText,
                              this._replaceButton, this._replaceAllButton ]) {
            SearchReplace.setDisabled(element, disable);
        }
    }

    static setDisabled(element, disable) {
        if (element.localName === "input") {
            if (disable) {
                element.setAttribute("disabled", "disabled");
            } else {
                element.removeAttribute("disabled");
            }
        } else {
            if (disable) {
                element.classList.add("xui-control-disabled");
            } else {
                element.classList.remove("xui-control-disabled");
            }
        }
    }
}

SearchReplace.TEMPLATE = document.createElement("template");
SearchReplace.TEMPLATE.innerHTML = `
<input type="text" size="20" class="xui-control xxe-srt-searched"
       spellcheck="false" autocomplete="off" 
       placeholder="Find what" /> 
<span class="xxe-tool-button xxe-srt-previous" 
      title="Find previous occurrence"></span>
<span class="xxe-tool-button xxe-srt-next" 
      title="Find next occurrence"></span>
<input type="text" size="20" class="xui-control xxe-srt-replacement"
       spellcheck="false" autocomplete="off"  
       placeholder="Replace with" /> 
<span class="xxe-tool-button xxe-srt-replace" 
      title="Replace &amp; find"></span>
<span class="xxe-tool-button xxe-srt-replace-all" 
      title="Replace all"></span>
<span class="xxe-tool-button xxe-srt-settings" 
      title="Search options"></span>
`;

window.customElements.define("xxe-search-replace", SearchReplace);