summaryrefslogtreecommitdiffstats
path: root/extensions/FlagTypeComment
diff options
context:
space:
mode:
Diffstat (limited to 'extensions/FlagTypeComment')
-rw-r--r--extensions/FlagTypeComment/template/en/default/flag/type_comment.html.tmpl40
-rw-r--r--extensions/FlagTypeComment/web/js/ftc.js189
-rw-r--r--extensions/FlagTypeComment/web/styles/ftc.css58
3 files changed, 258 insertions, 29 deletions
diff --git a/extensions/FlagTypeComment/template/en/default/flag/type_comment.html.tmpl b/extensions/FlagTypeComment/template/en/default/flag/type_comment.html.tmpl
index 88d9d4dd7..56e03040a 100644
--- a/extensions/FlagTypeComment/template/en/default/flag/type_comment.html.tmpl
+++ b/extensions/FlagTypeComment/template/en/default/flag/type_comment.html.tmpl
@@ -20,35 +20,17 @@
# byron jones <glob@mozilla.com>
#%]
-[% IF ftc_flags.keys.size %]
- <script [% script_nonce FILTER none %]>
- YAHOO.util.Event.onDOMReady(function() {
- var selects = YAHOO.util.Dom.getElementsByClassName('flag_select');
- for (var i = 0; i < selects.length; i++) {
- YAHOO.util.Event.on(selects[i], 'change', ftc_on_change);
- }
- });
+<link rel="stylesheet" href="extensions/FlagTypeComment/web/styles/ftc.css">
+<script [% script_nonce FILTER none %] src="extensions/FlagTypeComment/web/js/ftc.js"></script>
- function ftc_on_change(ev) {
- var id = ev.target.id.split('-')[1];
- var state = ev.target.value;
- var commentEl = document.getElementById('comment');
- if (!commentEl) return;
- [% FOREACH type_id = ftc_flags.keys %]
- [% FOREACH state = ftc_states %]
- if ([% type_id FILTER none %] == id && '[% state FILTER js %]' == state) {
- var text = '[% ftc_flags.$type_id.$state FILTER js %]';
- var value = commentEl.value;
- if (value == text) {
- return;
- } else if (value == '') {
- commentEl.value = text;
- } else {
- commentEl.value = text + "\n\n" + value;
- }
- }
- [% END %]
+[% IF ftc_flags.keys.size %]
+ [%# plaintext templates from database %]
+ [% FOREACH type_id = ftc_flags.keys %]
+ [% FOREACH state = ftc_states %]
+ <meta name="ftc:[% type_id FILTER none %]:[% state FILTER html %]"
+ content="[% ftc_flags.$type_id.$state FILTER html_linebreak %]">
[% END %]
- }
- </script>
+ [% END %]
+ [%# HTML form templates from extensions %]
+ [% Hook.process("form") %]
[% END %]
diff --git a/extensions/FlagTypeComment/web/js/ftc.js b/extensions/FlagTypeComment/web/js/ftc.js
new file mode 100644
index 000000000..2682721bc
--- /dev/null
+++ b/extensions/FlagTypeComment/web/js/ftc.js
@@ -0,0 +1,189 @@
+/* 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/.
+ *
+ * This Source Code Form is "Incompatible With Secondary Licenses", as
+ * defined by the Mozilla Public License, v. 2.0. */
+
+/**
+ * Reference or define the Bugzilla app namespace.
+ * @namespace
+ */
+var Bugzilla = Bugzilla || {}; // eslint-disable-line no-var
+
+/**
+ * Provide the ability to insert a comment template when a patch's approval flag is selected.
+ */
+Bugzilla.FlagTypeComment = class FlagTypeComment {
+ /**
+ * Initialize a new FlagTypeComment instance.
+ */
+ constructor() {
+ this.templates = [...document.querySelectorAll('template.approval-request')];
+ this.$flags = document.querySelector('#flags');
+ this.$comment = document.querySelector('#comment');
+
+ if (this.$flags && this.$comment) {
+ this.selects = [...this.$flags.querySelectorAll('.flag_select')];
+ this.selects.forEach($select => $select.addEventListener('change', () => this.flag_onselect($select)));
+ this.$comment.form.addEventListener('submit', () => this.form_onsubmit());
+ }
+ }
+
+ /**
+ * Check if a `<fieldset>` is compatible with the given flag. For example, `approval‑mozilla‑beta` matches
+ * `<fieldset data-flags="approval‑mozilla‑beta approval‑mozilla‑release">` while `approval‑mozilla‑esr60`
+ * matches `<fieldset data-flags="approval‑mozilla‑esr*">`.
+ * @param {String} name Flag name, such as `approval‑mozilla‑beta`.
+ * @param {(HTMLFieldSetElement|HTMLTemplateElement)} $element `<fieldset>` or `<template>` element with the
+ * `data-flags` attribute which is a space-separated list of flag names (wildcard chars can be used).
+ * @returns {Boolean} Whether the `<fieldset>` is compatible.
+ */
+ check_compatibility(name, $element) {
+ return !!$element.dataset.flags.split(' ')
+ .find(_name => !!name.match(new RegExp(`^${_name.replace('*', '.+')}$`, 'i')));
+ }
+
+ /**
+ * Return a list of temporary `<fieldset>`s already inserted to the current page.
+ * @type {Array<HTMLFieldSetElement>}
+ */
+ get inserted_fieldsets() {
+ return [...this.$flags.parentElement.querySelectorAll('fieldset.approval-request')];
+ }
+
+ /**
+ * Find a temporary `<fieldset>` already inserted to the current page by a flag name.
+ * @param {String} name Flag name, such as `approval‑mozilla‑beta`.
+ * @returns {HTMLFieldSetElement} Any `<fieldset>` element.
+ */
+ find_inserted_fieldset(name) {
+ return this.inserted_fieldsets.find($fieldset => this.check_compatibility(name, $fieldset));
+ }
+
+ /**
+ * Find an available `<fieldset>` embedded in HTML by a flag name.
+ * @param {String} name Flag name, such as `approval‑mozilla‑beta`.
+ * @returns {HTMLFieldSetElement} Any `<fieldset>` element.
+ */
+ find_available_fieldset(name) {
+ for (const $template of this.templates) {
+ if (this.check_compatibility(name, $template)) {
+ const $fieldset = $template.content.cloneNode(true).querySelector('fieldset');
+
+ $fieldset.className = 'approval-request';
+ $fieldset.dataset.flags = $template.dataset.flags;
+
+ return $fieldset;
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * Find a `<select>` element for a requested flag that matches the given `<fieldset>`.
+ * @param {HTMLFieldSetElement} $fieldset `<fieldset>` element with the `data-flags` attribute.
+ * @returns {HTMLSelectElement} Any `<select>` element.
+ */
+ find_select($fieldset) {
+ return this.selects
+ .find($_select => $_select.value === '?' && this.check_compatibility($_select.dataset.name, $fieldset));
+ }
+
+ /**
+ * Add text to the comment box at the end of any existing comment.
+ * @param {String} text Comment text to be added.
+ */
+ add_comment(text) {
+ this.$comment.value = this.$comment.value.match(/\S+/g) ? [this.$comment.value, text].join('\n\n') : text;
+ }
+
+ /**
+ * Called whenever a flag selection is changed. Insert or remove a comment template.
+ * @param {HTMLSelectElement} $select `<select>` element that the `change` event is fired.
+ */
+ flag_onselect($select) {
+ const id = Number($select.dataset.id);
+ const { name } = $select.dataset;
+ const state = $select.value;
+ let $fieldset = this.find_inserted_fieldset(name);
+
+ // Remove the temporary `<fieldset>` if not required. One `<fieldset>` can support multiple flags, so, for example,
+ // if `approval‑mozilla‑release` is unselected but `approval‑mozilla‑beta` is still selected, keep it
+ if (state !== '?' && $fieldset && !this.find_select($fieldset)) {
+ $fieldset.remove();
+ }
+
+ // Insert a temporary `<fieldset>` if available
+ if (state === '?' && !$fieldset) {
+ $fieldset = this.find_available_fieldset(name);
+
+ if ($fieldset) {
+ this.$flags.parentElement.appendChild($fieldset);
+ }
+ }
+
+ // Insert a traditional plaintext comment template if available
+ if (!$fieldset) {
+ const $meta = document.querySelector(`meta[name="ftc:${id}:${state}"]`);
+ const text = $meta ? $meta.content : '';
+
+ if (text && this.$comment.value !== text) {
+ this.add_comment(text);
+ }
+ }
+ }
+
+ /**
+ * Convert the input values into comment text and remove the temporary `<fieldset>` before submitting the form.
+ * @returns {Boolean} Always `true` to allow submitting the form.
+ */
+ form_onsubmit() {
+ for (const $fieldset of this.inserted_fieldsets) {
+ const text = [
+ `[${$fieldset.querySelector('legend').innerText}]`,
+ ...[...$fieldset.querySelectorAll('tr')].map($tr => {
+ const checkboxes = [...$tr.querySelectorAll('input[type="checkbox"]:checked')];
+ const $radio = $tr.querySelector('input[type="radio"]:checked');
+ const $input = $tr.querySelector('textarea,select,input');
+ const label = $tr.querySelector('th').innerText.replace(/\n/g, ' ');
+ let value = '';
+
+ if (checkboxes.length) {
+ value = checkboxes.map($checkbox => $checkbox.value.trim()).join(', ');
+ } else if ($radio) {
+ value = $radio.value.trim();
+ } else if ($input) {
+ value = $input.value.trim();
+
+ if ($input.dataset.type === 'bug') {
+ if (!value) {
+ value = 'None';
+ } else if (!isNaN(value)) {
+ value = `Bug ${value}`;
+ }
+ }
+
+ if ($input.dataset.type === 'bugs') {
+ if (!value) {
+ value = 'None';
+ } else {
+ value = value.split(/,\s*/).map(str => (!isNaN(str) ? `Bug ${str}` : str)).join(', ');
+ }
+ }
+ }
+
+ return `${label}: ${value}`;
+ }),
+ ].join('\n\n');
+
+ this.add_comment(text);
+ $fieldset.remove();
+ }
+
+ return true;
+ }
+};
+
+window.addEventListener('DOMContentLoaded', () => new Bugzilla.FlagTypeComment());
diff --git a/extensions/FlagTypeComment/web/styles/ftc.css b/extensions/FlagTypeComment/web/styles/ftc.css
new file mode 100644
index 000000000..93d190935
--- /dev/null
+++ b/extensions/FlagTypeComment/web/styles/ftc.css
@@ -0,0 +1,58 @@
+/* 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/.
+ *
+ * This Source Code Form is "Incompatible With Secondary Licenses", as
+ * defined by the Mozilla Public License, v. 2.0. */
+
+fieldset.approval-request {
+ margin: 1em 0;
+}
+
+fieldset.approval-request legend {
+ font-weight: bold;
+}
+
+fieldset.approval-request th {
+ padding: .4em;
+ width: 20em;
+ line-height: 1.25;
+ font-weight: normal;
+ text-align: right;
+ vertical-align: top;
+ white-space: normal !important;
+}
+
+fieldset.approval-request td {
+ vertical-align: middle;
+}
+
+fieldset.approval-request label {
+ font-weight: normal !important;
+}
+
+fieldset.approval-request input[type="text"] {
+ width: 10em;
+}
+
+fieldset.approval-request input.long[type="text"] {
+ width: 40em;
+}
+
+fieldset.approval-request textarea {
+ width: 40em;
+ min-height: 5em;
+ font-size: inherit;
+ line-height: inherit;
+ font-family: inherit;
+ resize: vertical;
+}
+
+fieldset.approval-request div {
+ padding: 0 !important;
+}
+
+fieldset.approval-request table ~ p:last-child {
+ margin: .4em;
+ text-align: right;
+}