tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

tabnote-menu.js (7617B)


      1 /* This Source Code Form is subject to the terms of the Mozilla Public
      2 * License, v. 2.0. If a copy of the MPL was not distributed with this
      3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
      4 
      5 "use strict";
      6 
      7 // This is loaded into chrome windows with the subscript loader. Wrap in
      8 // a block to prevent accidentally leaking globals onto `window`.
      9 {
     10  const { TabNotes } = ChromeUtils.importESModule(
     11    "moz-src:///browser/components/tabnotes/TabNotes.sys.mjs"
     12  );
     13 
     14  const OVERFLOW_WARNING_THRESHOLD = 980;
     15  const OVERFLOW_MAX_THRESHOLD = 1000;
     16 
     17  const OverflowState = {
     18    NONE: "none",
     19    WARN: "warn",
     20    OVERFLOW: "overflow",
     21  };
     22 
     23  class MozTabbrowserTabNoteMenu extends MozXULElement {
     24    static markup = /*html*/ `
     25    <panel
     26        id="tabNotePanel"
     27        type="arrow"
     28        titlebar="normal"
     29        class="tab-note-editor-panel"
     30        orient="vertical"
     31        role="dialog"
     32        ignorekeys="true"
     33        norolluponanchor="true"
     34        aria-labelledby="tab-note-editor-title"
     35        consumeoutsideclicks="false">
     36 
     37        <html:div class="panel-header" >
     38          <html:h1
     39            id="tab-note-editor-title">
     40          </html:h1>
     41        </html:div>
     42 
     43        <toolbarseparator />
     44 
     45        <html:div
     46          class="panel-body
     47          tab-note-editor-name">
     48          <html:textarea
     49            id="tab-note-text"
     50            name="tab-note-text"
     51            rows="3"
     52            value=""
     53            data-l10n-id="tab-note-editor-text-field"
     54          ></html:textarea>
     55        </html:div>
     56 
     57        <html:div
     58          class="panel-action-row">
     59          <html:div
     60            id="tab-note-overflow-indicator">
     61          </html:div>
     62          <html:moz-button-group
     63              class="tab-note-create-actions tab-note-create-mode-only"
     64              id="tab-note-default-actions">
     65              <html:moz-button
     66                  id="tab-note-editor-button-cancel"
     67                  data-l10n-id="tab-note-editor-button-cancel">
     68              </html:moz-button>
     69              <html:moz-button
     70                  type="primary"
     71                  id="tab-note-editor-button-save"
     72                  data-l10n-id="tab-note-editor-button-save">
     73              </html:moz-button>
     74          </html:moz-button-group>
     75        </html:div>
     76 
     77    </panel>
     78       `;
     79 
     80    #initialized = false;
     81    #panel;
     82    #noteField;
     83    #titleNode;
     84    /** @type {MozTabbrowserTab} */
     85    #currentTab = null;
     86    /** @type {boolean} */
     87    #createMode;
     88    #cancelButton;
     89    #saveButton;
     90    #overflowIndicator;
     91    /** @type {TabNoteTelemetrySource|null} */
     92    #telemetrySource = null;
     93 
     94    connectedCallback() {
     95      if (this.#initialized) {
     96        return;
     97      }
     98 
     99      this.textContent = "";
    100      this.appendChild(this.constructor.fragment);
    101      this.initializeAttributeInheritance();
    102 
    103      this.#panel = this.querySelector("panel");
    104      this.#noteField = document.getElementById("tab-note-text");
    105      this.#titleNode = document.getElementById("tab-note-editor-title");
    106      this.#cancelButton = this.querySelector("#tab-note-editor-button-cancel");
    107      this.#saveButton = this.querySelector("#tab-note-editor-button-save");
    108      this.#overflowIndicator = this.querySelector(
    109        "#tab-note-overflow-indicator"
    110      );
    111 
    112      this.#cancelButton.addEventListener("click", () => {
    113        this.#panel.hidePopup();
    114      });
    115      this.#saveButton.addEventListener("click", () => {
    116        this.saveNote();
    117      });
    118      this.#panel.addEventListener("keypress", this);
    119      this.#panel.addEventListener("popuphidden", this);
    120      this.#noteField.addEventListener("input", this);
    121 
    122      this.#initialized = true;
    123    }
    124 
    125    on_keypress(event) {
    126      if (event.defaultPrevented) {
    127        // The event has already been consumed inside of the panel.
    128        return;
    129      }
    130 
    131      switch (event.keyCode) {
    132        case KeyEvent.DOM_VK_ESCAPE:
    133          this.#panel.hidePopup();
    134          break;
    135        case KeyEvent.DOM_VK_RETURN:
    136          if (!event.shiftKey) {
    137            this.saveNote();
    138          }
    139          break;
    140      }
    141    }
    142 
    143    on_input() {
    144      this.#updatePanel();
    145    }
    146 
    147    on_popuphidden() {
    148      this.#currentTab = null;
    149      this.#noteField.value = "";
    150      this.#telemetrySource = null;
    151    }
    152 
    153    get createMode() {
    154      return this.#createMode;
    155    }
    156 
    157    set createMode(createModeEnabled) {
    158      if (this.#createMode == createModeEnabled) {
    159        return;
    160      }
    161      let headerL10nId = createModeEnabled
    162        ? "tab-note-editor-title-create"
    163        : "tab-note-editor-title-edit";
    164      this.#titleNode.innerText =
    165        gBrowser.tabLocalization.formatValueSync(headerL10nId);
    166      this.#createMode = createModeEnabled;
    167    }
    168 
    169    get #panelPosition() {
    170      if (gBrowser.tabContainer.verticalMode) {
    171        return SidebarController._positionStart
    172          ? "topleft topright"
    173          : "topright topleft";
    174      }
    175      return "bottomleft topleft";
    176    }
    177 
    178    #updatePanel() {
    179      const inputLength = this.#noteField.value.length;
    180      let overflow;
    181      if (inputLength > OVERFLOW_MAX_THRESHOLD) {
    182        overflow = OverflowState.OVERFLOW;
    183      } else if (inputLength > OVERFLOW_WARNING_THRESHOLD) {
    184        overflow = OverflowState.WARN;
    185      } else {
    186        overflow = OverflowState.NONE;
    187      }
    188 
    189      this.#saveButton.disabled =
    190        overflow == OverflowState.OVERFLOW || !inputLength;
    191 
    192      if (overflow != OverflowState.NONE) {
    193        this.#panel.setAttribute("overflow", overflow);
    194        this.#overflowIndicator.innerText =
    195          gBrowser.tabLocalization.formatValueSync(
    196            "tab-note-editor-character-limit",
    197            {
    198              totalCharacters: inputLength,
    199              maxAllowedCharacters: OVERFLOW_MAX_THRESHOLD,
    200            }
    201          );
    202      } else {
    203        this.#panel.removeAttribute("overflow");
    204      }
    205 
    206      // Manually adjust panel height and scroll behaviour to compensate for input size
    207      // CSS has a `field-sizing` attribute that does this automatically,
    208      // but it is not yet supported.
    209      // TODO bug2006439: Replace this with `field-sizing` after the implementation of bug1832409
    210      this.#noteField.style.height = "auto";
    211      this.#noteField.style.height = `${this.#noteField.scrollHeight}px`;
    212    }
    213 
    214    /**
    215     * @param {MozTabbrowserTab} tab
    216     *   The tab whose note this panel will control.
    217     * @param {object} [options]
    218     * @param {TabNoteTelemetrySource} [options.telemetrySource]
    219     *   The UI surface that requested to open this panel.
    220     */
    221    openPanel(tab, options = {}) {
    222      if (!TabNotes.isEligible(tab)) {
    223        return;
    224      }
    225      this.#currentTab = tab;
    226      this.#telemetrySource = options.telemetrySource;
    227 
    228      this.#updatePanel();
    229 
    230      TabNotes.get(tab).then(note => {
    231        if (note) {
    232          this.createMode = false;
    233          this.#noteField.value = note.text;
    234        } else {
    235          this.createMode = true;
    236        }
    237 
    238        this.#panel.addEventListener(
    239          "popupshown",
    240          () => {
    241            this.#noteField.focus();
    242          },
    243          {
    244            once: true,
    245          }
    246        );
    247        this.#panel.openPopup(tab, {
    248          position: this.#panelPosition,
    249        });
    250      });
    251    }
    252 
    253    saveNote() {
    254      let note = this.#noteField.value;
    255 
    256      if (TabNotes.isEligible(this.#currentTab) && note.length) {
    257        TabNotes.set(this.#currentTab, note, {
    258          telemetrySource: this.#telemetrySource,
    259        });
    260      }
    261 
    262      this.#panel.hidePopup();
    263    }
    264  }
    265 
    266  customElements.define("tabnote-menu", MozTabbrowserTabNoteMenu);
    267 }