312 lines
9.1 KiB
JavaScript
312 lines
9.1 KiB
JavaScript
/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
|
|
|
"use strict";
|
|
|
|
// This is loaded into chrome windows with the subscript loader. Wrap in
|
|
// a block to prevent accidentally leaking globals onto `window`.
|
|
{
|
|
const { TabNotes } = ChromeUtils.importESModule(
|
|
"moz-src:///browser/components/tabnotes/TabNotes.sys.mjs"
|
|
);
|
|
|
|
const OVERFLOW_WARNING_THRESHOLD = 980;
|
|
const OVERFLOW_MAX_THRESHOLD = 1000;
|
|
|
|
const OverflowState = {
|
|
NONE: "none",
|
|
WARN: "warn",
|
|
OVERFLOW: "overflow",
|
|
};
|
|
|
|
class MozTabbrowserTabNoteMenu extends MozXULElement {
|
|
static markup = /*html*/ `
|
|
<panel
|
|
id="tabNotePanel"
|
|
type="arrow"
|
|
titlebar="normal"
|
|
class="tab-note-editor-panel panel-no-padding"
|
|
orient="vertical"
|
|
role="dialog"
|
|
ignorekeys="true"
|
|
norolluponanchor="true"
|
|
consumeoutsideclicks="false">
|
|
|
|
<html:div class="panel-header" id="tab-note-editor-header" >
|
|
<html:h1
|
|
id="tab-note-editor-title">
|
|
</html:h1>
|
|
</html:div>
|
|
|
|
<toolbarseparator id="tab-note-editor-separator" />
|
|
|
|
<html:div
|
|
class="panel-subview-body
|
|
tab-note-editor-name">
|
|
<html:textarea
|
|
id="tab-note-text"
|
|
name="tab-note-text"
|
|
rows="3"
|
|
value=""
|
|
data-l10n-id="tab-note-editor-text-field"
|
|
></html:textarea>
|
|
</html:div>
|
|
|
|
<html:div
|
|
class="panel-action-row panel-footer">
|
|
<html:moz-button
|
|
id="tab-note-editor-button-delete"
|
|
type="icon ghost"
|
|
data-l10n-id="tab-note-editor-button-delete">
|
|
</html:moz-button>
|
|
<html:div
|
|
id="tab-note-overflow-indicator">
|
|
</html:div>
|
|
<html:moz-button-group
|
|
id="tab-note-default-actions">
|
|
<html:moz-button
|
|
id="tab-note-editor-button-cancel"
|
|
data-l10n-id="tab-note-editor-button-cancel">
|
|
</html:moz-button>
|
|
<html:moz-button
|
|
type="primary"
|
|
id="tab-note-editor-button-save"
|
|
data-l10n-id="tab-note-editor-button-save">
|
|
</html:moz-button>
|
|
</html:moz-button-group>
|
|
</html:div>
|
|
|
|
</panel>
|
|
`;
|
|
|
|
#initialized = false;
|
|
#panel;
|
|
#noteField;
|
|
#headerEl;
|
|
#separatorEl;
|
|
#titleNode;
|
|
/** @type {MozTabbrowserTab} */
|
|
#currentTab = null;
|
|
/** @type {boolean|null} */
|
|
#createMode = null;
|
|
#cancelButton;
|
|
#saveButton;
|
|
#deleteButton;
|
|
#overflowIndicator;
|
|
/** @type {TabNoteTelemetrySource|null} */
|
|
#telemetrySource = null;
|
|
|
|
connectedCallback() {
|
|
if (this.#initialized) {
|
|
return;
|
|
}
|
|
|
|
this.textContent = "";
|
|
this.appendChild(this.constructor.fragment);
|
|
this.initializeAttributeInheritance();
|
|
|
|
this.#panel = this.querySelector("panel");
|
|
this.#noteField = document.getElementById("tab-note-text");
|
|
this.#headerEl = this.querySelector("#tab-note-editor-header");
|
|
this.#separatorEl = this.querySelector("#tab-note-editor-separator");
|
|
this.#titleNode = this.querySelector("#tab-note-editor-title");
|
|
this.#cancelButton = this.querySelector("#tab-note-editor-button-cancel");
|
|
this.#saveButton = this.querySelector("#tab-note-editor-button-save");
|
|
this.#deleteButton = this.querySelector("#tab-note-editor-button-delete");
|
|
this.#overflowIndicator = this.querySelector(
|
|
"#tab-note-overflow-indicator"
|
|
);
|
|
|
|
this.#cancelButton.addEventListener("click", () => {
|
|
this.#panel.hidePopup();
|
|
});
|
|
this.#saveButton.addEventListener("click", () => {
|
|
this.saveNote();
|
|
});
|
|
this.#deleteButton.addEventListener("click", () => {
|
|
this.#deleteNote();
|
|
});
|
|
this.#panel.addEventListener("keypress", this);
|
|
this.#panel.addEventListener("popuphidden", this);
|
|
this.#noteField.addEventListener("input", this);
|
|
|
|
this.#initialized = true;
|
|
}
|
|
|
|
on_keypress(event) {
|
|
if (event.defaultPrevented) {
|
|
// The event has already been consumed inside of the panel.
|
|
return;
|
|
}
|
|
|
|
switch (event.keyCode) {
|
|
case KeyEvent.DOM_VK_ESCAPE:
|
|
this.#panel.hidePopup();
|
|
break;
|
|
case KeyEvent.DOM_VK_RETURN:
|
|
if (!event.shiftKey && event.target === this.#noteField) {
|
|
this.saveNote();
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
on_input() {
|
|
this.#updatePanel();
|
|
}
|
|
|
|
on_popuphidden() {
|
|
this.#currentTab = null;
|
|
this.#noteField.value = "";
|
|
this.#telemetrySource = null;
|
|
}
|
|
|
|
get createMode() {
|
|
return this.#createMode;
|
|
}
|
|
|
|
set createMode(createModeEnabled) {
|
|
if (this.#createMode == createModeEnabled) {
|
|
return;
|
|
}
|
|
let headerL10nId = createModeEnabled
|
|
? "tab-note-editor-title-create"
|
|
: "tab-note-editor-title-edit";
|
|
this.#titleNode.innerText =
|
|
gBrowser.tabLocalization.formatValueSync(headerL10nId);
|
|
this.#headerEl.hidden = !createModeEnabled;
|
|
this.#separatorEl.hidden = !createModeEnabled;
|
|
this.#deleteButton.hidden = createModeEnabled;
|
|
this.#panel.setAttribute(
|
|
"aria-label",
|
|
gBrowser.tabLocalization.formatValueSync(headerL10nId)
|
|
);
|
|
|
|
this.#createMode = createModeEnabled;
|
|
}
|
|
|
|
get #panelPosition() {
|
|
if (gBrowser.tabContainer.verticalMode) {
|
|
return SidebarController._positionStart
|
|
? "topleft topright"
|
|
: "topright topleft";
|
|
}
|
|
return "bottomleft topleft";
|
|
}
|
|
|
|
#updatePanel() {
|
|
const inputLength = this.#noteField.value.length;
|
|
const trimmedLength = this.#noteField.value.trim().length;
|
|
|
|
let overflow;
|
|
if (inputLength > OVERFLOW_MAX_THRESHOLD) {
|
|
overflow = OverflowState.OVERFLOW;
|
|
} else if (inputLength > OVERFLOW_WARNING_THRESHOLD) {
|
|
overflow = OverflowState.WARN;
|
|
} else {
|
|
overflow = OverflowState.NONE;
|
|
}
|
|
|
|
this.#saveButton.disabled =
|
|
overflow == OverflowState.OVERFLOW || trimmedLength === 0;
|
|
|
|
if (overflow != OverflowState.NONE) {
|
|
this.#panel.setAttribute("overflow", overflow);
|
|
this.#overflowIndicator.innerText =
|
|
gBrowser.tabLocalization.formatValueSync(
|
|
"tab-note-editor-character-limit",
|
|
{
|
|
totalCharacters: inputLength,
|
|
maxAllowedCharacters: OVERFLOW_MAX_THRESHOLD,
|
|
}
|
|
);
|
|
} else {
|
|
this.#panel.removeAttribute("overflow");
|
|
}
|
|
|
|
// Manually adjust panel height and scroll behaviour to compensate for input size
|
|
// CSS has a `field-sizing` attribute that does this automatically,
|
|
// but it is not yet supported.
|
|
// TODO bug2006439: Replace this with `field-sizing` after the implementation of bug1832409
|
|
this.#noteField.style.height = "auto"; // Reset height so previous manual adjustments do not affect calculations
|
|
let computedStyle = getComputedStyle(this.#noteField);
|
|
let contentHeight =
|
|
this.#noteField.scrollHeight -
|
|
parseFloat(computedStyle.paddingTop) -
|
|
parseFloat(computedStyle.paddingBottom);
|
|
this.#noteField.style.height = `${contentHeight}px`;
|
|
}
|
|
|
|
/**
|
|
* @param {MozTabbrowserTab} tab
|
|
* The tab whose note this panel will control.
|
|
* @param {object} [options]
|
|
* @param {TabNoteTelemetrySource} [options.telemetrySource]
|
|
* The UI surface that requested to open this panel.
|
|
*/
|
|
openPanel(tab, options = {}) {
|
|
if (!TabNotes.isEligible(tab)) {
|
|
return;
|
|
}
|
|
// Lazily set the icon to avoid loading it at startup
|
|
if (!this.#deleteButton.iconSrc) {
|
|
this.#deleteButton.iconSrc = "chrome://global/skin/icons/delete.svg";
|
|
}
|
|
this.#currentTab = tab;
|
|
this.#telemetrySource = options.telemetrySource;
|
|
|
|
TabNotes.get(tab).then(note => {
|
|
if (note) {
|
|
this.createMode = false;
|
|
this.#noteField.value = note.text;
|
|
} else {
|
|
this.createMode = true;
|
|
}
|
|
|
|
this.#panel.addEventListener(
|
|
"popupshown",
|
|
() => {
|
|
this.#noteField.focus();
|
|
},
|
|
{
|
|
once: true,
|
|
}
|
|
);
|
|
this.#panel.openPopup(tab, {
|
|
position: this.#panelPosition,
|
|
});
|
|
|
|
this.#updatePanel();
|
|
});
|
|
}
|
|
|
|
saveNote() {
|
|
let note = this.#noteField.value;
|
|
|
|
if (
|
|
TabNotes.isEligible(this.#currentTab) &&
|
|
note.trim().length &&
|
|
note.length <= OVERFLOW_MAX_THRESHOLD
|
|
) {
|
|
TabNotes.set(this.#currentTab, note, {
|
|
telemetrySource: this.#telemetrySource,
|
|
});
|
|
}
|
|
|
|
this.#panel.hidePopup();
|
|
}
|
|
|
|
#deleteNote() {
|
|
if (TabNotes.isEligible(this.#currentTab)) {
|
|
TabNotes.delete(this.#currentTab, {
|
|
telemetrySource: this.#telemetrySource,
|
|
});
|
|
}
|
|
this.#panel.hidePopup();
|
|
}
|
|
}
|
|
|
|
customElements.define("tabnote-menu", MozTabbrowserTabNoteMenu);
|
|
}
|