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 }