refactor(browser-extension): report dialog to allow user input in both reason and url fields

This commit is contained in:
wanhose 2024-08-25 13:55:38 +02:00
parent 23e93f43af
commit ecbdb3a00d
2 changed files with 265 additions and 181 deletions

View File

@ -2,19 +2,6 @@ if (typeof browser === 'undefined') {
browser = chrome; browser = chrome;
} }
/**
* @description Report reasons
* @type {string[]}
*/
const reasons = [
'Cannot click',
'Page contains visual glitches',
'Page is blank',
'Page is laggy',
'Page is not responding',
'Popup showed up',
];
/** /**
* @description Report dialog ID * @description Report dialog ID
*/ */
@ -48,63 +35,48 @@ const reportDialogHtml = `
${browser.i18n.getMessage('reportDialog_bodyText')} ${browser.i18n.getMessage('reportDialog_bodyText')}
</div> </div>
<div class="report-dialog-form"> <div class="report-dialog-form">
<div class="report-dialog-radio-group"> <div class="report-dialog-input-group">
<div <div class="report-dialog-input-label" id="report-dialog-label-url">
aria-checked="false" ${browser.i18n.getMessage('reportDialog_urlInputLabel')}
class="report-dialog-radio" <span class="report-dialog-input-label-required">*</span>
data-value="0"
role="radio"
tabindex="0"
>
${browser.i18n.getMessage('reportDialog_cannotClickOption')}
</div> </div>
<div <div
aria-checked="false" aria-labelledby="report-dialog-label-url"
class="report-dialog-radio" aria-multiline="false"
data-value="1" aria-required="true"
role="radio" class="report-dialog-input"
contenteditable="true"
id="report-dialog-input-url"
role="textbox"
tabindex="0" tabindex="0"
> ></div>
${browser.i18n.getMessage('reportDialog_pageVisualGlitchOption')} <div class="report-dialog-input-error" id="report-dialog-input-url-error">
${browser.i18n.getMessage('reportDialog_urlInputError')}
</div>
</div>
<div class="report-dialog-input-group">
<div class="report-dialog-input-label" id="report-dialog-label-reason">
${browser.i18n.getMessage('reportDialog_reasonInputLabel')}
<span class="report-dialog-input-label-required">*</span>
</div> </div>
<div <div
aria-checked="false" aria-labelledby="report-dialog-label-reason"
class="report-dialog-radio" aria-multiline="true"
data-value="2" aria-placeholder="${browser.i18n.getMessage('reportDialog_reasonInputLabel')}"
role="radio" aria-required="true"
class="report-dialog-input"
contenteditable="true"
id="report-dialog-input-reason"
role="textbox"
tabindex="0" tabindex="0"
> >
${browser.i18n.getMessage('reportDialog_blankPageOption')} ${browser.i18n.getMessage('reportDialog_reasonInputPlaceholder')}
</div> </div>
<div <div class="report-dialog-input-error" id="report-dialog-input-reason-error">
aria-checked="false" ${browser.i18n.getMessage('reportDialog_reasonInputError')}
class="report-dialog-radio"
data-value="3"
role="radio"
tabindex="0"
>
${browser.i18n.getMessage('reportDialog_laggyPageOption')}
</div>
<div
aria-checked="false"
class="report-dialog-radio"
data-value="4"
role="radio"
tabindex="0"
>
${browser.i18n.getMessage('reportDialog_pageNotRespondingOption')}
</div>
<div
aria-checked="false"
class="report-dialog-radio"
data-value="5"
role="radio"
tabindex="0"
>
${browser.i18n.getMessage('reportDialog_popupShowUpOption')}
</div> </div>
</div> </div>
<div aria-disabled="true" class="report-dialog-submit-button" role="button" tabindex="0"> <div class="report-dialog-submit-button" role="button" tabindex="0">
${browser.i18n.getMessage('contextMenu_reportOption')?.replace('...', '')} ${browser.i18n.getMessage('contextMenu_reportOption')?.replace('...', '')}
</div> </div>
</div> </div>
@ -156,22 +128,38 @@ function hideReportDialog() {
} }
/** /**
* @description Dialog radio input click handler * @description Input change handler
* @param {MouseEvent} event * @param {InputEvent} event
*/ */
function radioClickHandler(event) { function inputChangeHandler(event) {
const dialog = document.getElementById(reportDialogId); event.currentTarget.removeAttribute('aria-invalid');
const radios = dialog.getElementsByClassName('report-dialog-radio'); }
const submitButton = dialog?.getElementsByClassName('report-dialog-submit-button')[0];
for (const radio of radios) {
radio.setAttribute('aria-checked', 'false');
}
/**
* @description Input key down handler
* @param {KeyboardEvent} event
*/
function inputKeyDownHandler(event) {
if (event.key === 'Enter') {
event.preventDefault(); event.preventDefault();
event.currentTarget.setAttribute('aria-checked', 'true'); event.currentTarget.blur();
submitButton.setAttribute('aria-disabled', 'false'); }
submitButton.addEventListener('click', submitButtonClickHandler); }
/**
* @description Input paste handler
* @param {ClipboardEvent} event
*/
function inputPasteHandler(event) {
event.preventDefault();
const text = event.clipboardData?.getData('text').replace(/\r?\n|\r/g, ' ');
const selection = window.getSelection();
if (selection.rangeCount) {
selection.deleteFromDocument();
selection.getRangeAt(0).insertNode(document.createTextNode(text));
}
} }
/** /**
@ -181,7 +169,12 @@ function showReportDialog() {
const existingDialog = document.getElementById(reportDialogId); const existingDialog = document.getElementById(reportDialogId);
if (existingDialog) { if (existingDialog) {
const submitButton = existingDialog.getElementsByClassName('report-dialog-submit-button')[0];
const urlInput = existingDialog.querySelector('#report-dialog-input-url');
existingDialog.showModal(); existingDialog.showModal();
submitButton.setAttribute('aria-disabled', 'false');
urlInput.textContent = window.location.origin + window.location.pathname;
return; return;
} }
@ -190,16 +183,23 @@ function showReportDialog() {
const dialog = result.body.firstElementChild; const dialog = result.body.firstElementChild;
const closeButton = dialog.getElementsByClassName('report-dialog-close-button')[0]; const closeButton = dialog.getElementsByClassName('report-dialog-close-button')[0];
const link = document.createElement('link'); const link = document.createElement('link');
const radios = dialog.getElementsByClassName('report-dialog-radio'); const reasonInput = dialog?.querySelector('#report-dialog-input-reason');
const submitButton = dialog?.getElementsByClassName('report-dialog-submit-button')[0];
const urlInput = dialog?.querySelector('#report-dialog-input-url');
closeButton.addEventListener('click', closeButtonClickHandler); closeButton.addEventListener('click', closeButtonClickHandler);
dialog.querySelector('#report-dialog-input-url').textContent =
window.location.origin + window.location.pathname;
link.setAttribute('href', 'https://fonts.googleapis.com/css?family=Inter'); link.setAttribute('href', 'https://fonts.googleapis.com/css?family=Inter');
link.setAttribute('id', 'report-dialog-font'); link.setAttribute('id', 'report-dialog-font');
link.setAttribute('rel', 'stylesheet'); link.setAttribute('rel', 'stylesheet');
reasonInput.addEventListener('input', inputChangeHandler);
for (const radio of radios) { reasonInput.addEventListener('keydown', inputKeyDownHandler);
radio.addEventListener('click', radioClickHandler); reasonInput.addEventListener('paste', inputPasteHandler);
} submitButton.addEventListener('click', submitButtonClickHandler);
urlInput.addEventListener('input', inputChangeHandler);
urlInput.addEventListener('keydown', inputKeyDownHandler);
urlInput.addEventListener('paste', inputPasteHandler);
dispatch({ type: 'INSERT_DIALOG_CSS' }); dispatch({ type: 'INSERT_DIALOG_CSS' });
document.body.appendChild(dialog); document.body.appendChild(dialog);
@ -212,31 +212,77 @@ function showReportDialog() {
* @param {MouseEvent} event * @param {MouseEvent} event
*/ */
async function submitButtonClickHandler(event) { async function submitButtonClickHandler(event) {
const target = event.currentTarget; event.preventDefault();
if (target.getAttribute('aria-disabled') === 'true') { if (event.currentTarget.getAttribute('aria-disabled') === 'true') {
return; return;
} }
event.preventDefault(); event.currentTarget.setAttribute('aria-disabled', 'true');
target.setAttribute('aria-disabled', 'true');
const dialog = document.getElementById(reportDialogId); const dialog = document.getElementById(reportDialogId);
const reasonInput = dialog?.querySelector('#report-dialog-input-reason');
const reasonText = reasonInput?.textContent.trim();
const urlInput = dialog?.querySelector('#report-dialog-input-url');
const urlText = urlInput?.textContent.trim();
const errors = validateForm({ reason: reasonText, url: urlText });
if (errors) {
if (errors.reason) {
reasonInput?.setAttribute('aria-invalid', 'true');
reasonInput?.setAttribute('aria-errormessage', 'report-dialog-input-reason-error');
}
if (errors.url) {
urlInput?.setAttribute('aria-invalid', 'true');
urlInput?.setAttribute('aria-errormessage', 'report-dialog-input-url-error');
}
event.currentTarget.setAttribute('aria-disabled', 'false');
return;
}
const formView = dialog?.getElementsByClassName('report-dialog-form-view')[0]; const formView = dialog?.getElementsByClassName('report-dialog-form-view')[0];
const issueButton = dialog?.getElementsByClassName('report-dialog-issue-button')[0]; const issueButton = dialog?.getElementsByClassName('report-dialog-issue-button')[0];
const option = dialog?.querySelector('.report-dialog-radio[aria-checked="true"]');
const reasonIndex = option?.dataset.value;
const reason = Number.isNaN(reasonIndex) ? 'Unknown' : reasons[reasonIndex];
const submitView = dialog?.getElementsByClassName('report-dialog-submit-view')[0]; const submitView = dialog?.getElementsByClassName('report-dialog-submit-view')[0];
const userAgent = window.navigator.userAgent; const userAgent = window.navigator.userAgent;
const issueUrl = await dispatch({ userAgent, reason, type: 'REPORT' }); const issueUrl = await dispatch({ userAgent, reason: reasonText, url: urlText, type: 'REPORT' });
formView?.setAttribute('hidden', 'true'); formView?.setAttribute('hidden', 'true');
issueButton?.addEventListener('click', () => window.open(issueUrl, '_blank')); issueButton?.addEventListener('click', () => window.open(issueUrl, '_blank'));
submitView?.removeAttribute('hidden'); submitView?.removeAttribute('hidden');
} }
/**
* @description Validate form
* @param {{ reason: string | undefined | undefined, url: string | undefined }} params
* @returns {{ reason: string | undefined, url: string | undefined } | undefined}
*/
function validateForm(params) {
const { reason, url } = params;
let errors = undefined;
if (!reason || reason.length < 10 || reason.length > 1000) {
errors = {
...(errors ?? {}),
reason: browser.i18n.getMessage('reportDialog_reasonInputError'),
};
}
try {
new URL(url);
} catch {
errors = {
...(errors ?? {}),
url: browser.i18n.getMessage('reportDialog_urlInputError'),
};
}
return errors;
}
/** /**
* @description Listen to messages * @description Listen to messages
*/ */

View File

@ -1,4 +1,5 @@
:root { :root {
--cookie-dialog-monster-color-error: #cc0000;
--cookie-dialog-monster-color-primary: #3dd9eb; --cookie-dialog-monster-color-primary: #3dd9eb;
--cookie-dialog-monster-color-secondary: #34495e; --cookie-dialog-monster-color-secondary: #34495e;
--cookie-dialog-monster-color-success: #5cb85c; --cookie-dialog-monster-color-success: #5cb85c;
@ -26,23 +27,41 @@
#report-dialog * { #report-dialog * {
box-sizing: border-box; box-sizing: border-box;
visibility: visible;
} }
.report-dialog-body {
#report-dialog div {
all: unset;
display: block;
box-sizing: border-box;
}
#report-dialog *::-moz-selection {
color: var(--cookie-dialog-monster-color-white);
background: var(--cookie-dialog-monster-color-tertiary);
}
#report-dialog *::selection {
color: var(--cookie-dialog-monster-color-white);
background: var(--cookie-dialog-monster-color-tertiary);
}
#report-dialog .report-dialog-body {
display: block; display: block;
font-size: 14px; font-size: 14px;
line-height: 14px; line-height: 1.2;
padding: 16px; padding: 16px;
} }
.report-dialog-body-text { #report-dialog .report-dialog-body-text {
display: block; display: block;
font-family: Inter, Arial, Helvetica, sans-serif; font-family: Inter, Arial, Helvetica, sans-serif;
font-size: 14px;
line-height: 1.2;
margin: 0px; margin: 0px;
padding: 0px; padding: 0px;
} }
.report-dialog-close-button { #report-dialog .report-dialog-close-button {
align-items: center; align-items: center;
background-color: transparent; background-color: transparent;
border: none; border: none;
@ -53,119 +72,138 @@
margin: 0px; margin: 0px;
outline: none; outline: none;
padding: 0px; padding: 0px;
transition: 0.4s;
} }
.report-dialog-close-button { #report-dialog .report-dialog-close-button {
stroke: var(--cookie-dialog-monster-color-white); stroke: var(--cookie-dialog-monster-color-white);
} }
.report-dialog-close-button:focus, #report-dialog .report-dialog-close-button:focus,
.report-dialog-close-button:hover > svg { #report-dialog .report-dialog-close-button:hover > svg {
stroke: var(--cookie-dialog-monster-color-secondary); stroke: var(--cookie-dialog-monster-color-secondary);
} }
.report-dialog-close-button:focus-visible { #report-dialog .report-dialog-close-button:focus-visible {
box-shadow: initial; box-shadow: initial;
transition: initial; transition: initial;
} }
.report-dialog-close-button:focus, #report-dialog .report-dialog-close-button:focus,
.report-dialog-close-button:hover { #report-dialog .report-dialog-close-button:hover {
background-color: var(--cookie-dialog-monster-color-white); background-color: var(--cookie-dialog-monster-color-white);
} }
.report-dialog-form {
#report-dialog .report-dialog-form {
display: grid;
gap: 10px;
}
#report-dialog .report-dialog-form-view {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
gap: 24px; gap: 24px;
} }
.report-dialog-form-view { #report-dialog .report-dialog-form-view[hidden] {
display: flex;
flex-direction: column;
gap: 24px;
}
.report-dialog-form-view[hidden] {
display: none; display: none;
} }
.report-dialog-header {
#report-dialog .report-dialog-header {
align-items: center; align-items: center;
background-color: var(--cookie-dialog-monster-color-secondary); background-color: var(--cookie-dialog-monster-color-secondary);
display: flex; display: flex;
font-size: 16px; font-size: 16px;
height: 54px; height: 54px;
justify-content: space-between; justify-content: space-between;
line-height: 16px; line-height: 1.2;
padding: 16px; padding: 16px;
} }
.report-dialog-header-title { #report-dialog .report-dialog-header-title {
color: var(--cookie-dialog-monster-color-white); color: var(--cookie-dialog-monster-color-white);
font-family: Inter, Arial, Helvetica, sans-serif; font-family: Inter, Arial, Helvetica, sans-serif;
font-weight: 500; font-weight: 500;
} }
.report-dialog-radio {
color: var(--cookie-dialog-monster-color-tertiary);
cursor: pointer;
display: block;
font-family: Inter, Arial, Helvetica, sans-serif;
outline: none;
padding-left: 24px;
position: relative;
}
.report-dialog-radio[aria-checked='true']:after { #report-dialog .report-dialog-input {
opacity: 1;
transform: scale(1, 1);
}
.report-dialog-radio:after {
background-color: var(--cookie-dialog-monster-color-primary);
border-radius: 50%;
box-sizing: border-box;
content: '';
display: block;
height: 8px;
left: 3px;
opacity: 0;
position: absolute;
top: 3px;
transform: scale(0, 0);
transition: all 0.2s cubic-bezier(0.64, 0.57, 0.67, 1.53);
width: 8px;
}
.report-dialog-radio:focus:not([aria-checked='true']):after,
.report-dialog-radio:hover:not([aria-checked='true']):after {
background: var(--cookie-dialog-monster-color-tertiary);
opacity: 1;
transform: scale(1, 1);
}
.report-dialog-radio:before {
background: var(--cookie-dialog-monster-color-white);
border: 1px solid var(--cookie-dialog-monster-color-tertiary); border: 1px solid var(--cookie-dialog-monster-color-tertiary);
border-radius: 50%; border-radius: 4px;
box-sizing: border-box; color: var(--cookie-dialog-monster-color-secondary);
content: ''; cursor: text;
display: block; font-family: Inter, Arial, Helvetica, sans-serif;
height: 14px; font-size: 14px;
left: 0px; line-height: 1;
margin-right: 5px; outline: none;
position: absolute; padding: 12px 8px;
top: 0px;
width: 14px;
} }
.report-dialog-radio-group { #report-dialog .report-dialog-input:hover {
border-color: var(--cookie-dialog-monster-color-secondary);
}
#report-dialog .report-dialog-input:focus {
border-color: var(--cookie-dialog-monster-color-primary);
}
#report-dialog .report-dialog-input:focus-visible {
box-shadow: initial;
transition: initial;
}
#report-dialog .report-dialog-input::-webkit-scrollbar {
display: none;
}
#report-dialog .report-dialog-input[aria-invalid='true'] {
border-color: var(--cookie-dialog-monster-color-error);
}
#report-dialog .report-dialog-input[aria-invalid='true'] + .report-dialog-input-error {
visibility: visible;
}
#report-dialog .report-dialog-input[aria-multiline='false'] {
-ms-overflow-style: none;
display: flex; display: flex;
flex-direction: column; height: 40px;
gap: 18px; overflow-x: auto;
scrollbar-width: none;
text-wrap: nowrap;
} }
.report-dialog-issue-button, #report-dialog .report-dialog-input[aria-multiline='true'] {
.report-dialog-submit-button { -ms-overflow-style: none;
height: 120px;
overflow-y: auto;
scrollbar-width: none;
}
#report-dialog .report-dialog-input-error {
color: var(--cookie-dialog-monster-color-error);
font-family: Inter, Arial, Helvetica, sans-serif;
font-size: 10px;
line-height: 1.2;
visibility: hidden;
}
#report-dialog .report-dialog-input-group {
display: grid;
gap: 4px;
}
#report-dialog .report-dialog-input-label {
color: var(--cookie-dialog-monster-color-secondary);
font-family: Inter, Arial, Helvetica, sans-serif;
font-size: 12px;
line-height: 1.2;
}
#report-dialog .report-dialog-input-label-required {
color: var(--cookie-dialog-monster-color-error);
}
#report-dialog .report-dialog-issue-button,
#report-dialog .report-dialog-submit-button {
align-items: center; align-items: center;
background-color: var(--cookie-dialog-monster-color-secondary); background-color: var(--cookie-dialog-monster-color-secondary);
border: 1px solid var(--cookie-dialog-monster-color-secondary); border: 1px solid var(--cookie-dialog-monster-color-secondary);
@ -177,52 +215,52 @@
font-size: 14px; font-size: 14px;
height: 39px; height: 39px;
justify-content: center; justify-content: center;
line-height: 1.2;
outline: none; outline: none;
padding: 8px 16px; padding: 8px 16px;
text-align: center; text-align: center;
transition: 0.4s;
width: 100%; width: 100%;
} }
.report-dialog-issue-button:focus, #report-dialog .report-dialog-issue-button:focus,
.report-dialog-issue-button:hover, #report-dialog .report-dialog-issue-button:hover,
.report-dialog-submit-button:focus, #report-dialog .report-dialog-submit-button:focus,
.report-dialog-submit-button:hover { #report-dialog .report-dialog-submit-button:hover {
background-color: var(--cookie-dialog-monster-color-white); background-color: var(--cookie-dialog-monster-color-white);
color: var(--cookie-dialog-monster-color-secondary); color: var(--cookie-dialog-monster-color-secondary);
} }
.report-dialog-issue-button:focus-visible, #report-dialog .report-dialog-issue-button:focus-visible,
.report-dialog-submit-button:focus-visible { #report-dialog .report-dialog-submit-button:focus-visible {
box-shadow: initial; box-shadow: initial;
transition: initial; transition: initial;
} }
.report-dialog-issue-button[aria-disabled='true'], #report-dialog .report-dialog-issue-button[aria-disabled='true'],
.report-dialog-submit-button[aria-disabled='true'] { #report-dialog .report-dialog-submit-button[aria-disabled='true'] {
background-color: var(--cookie-dialog-monster-color-tertiary); background-color: var(--cookie-dialog-monster-color-tertiary);
border: 1px solid var(--cookie-dialog-monster-color-tertiary); border: 1px solid var(--cookie-dialog-monster-color-tertiary);
color: var(--cookie-dialog-monster-color-white); color: var(--cookie-dialog-monster-color-white);
cursor: not-allowed; cursor: not-allowed;
} }
.report-dialog-submit-extra-text { #report-dialog .report-dialog-submit-extra-text {
font-family: inherit; font-family: inherit;
font-size: 14px; font-size: 14px;
line-height: 16px; line-height: 1.2;
margin: 0px; margin: 0px;
text-align: justify; text-align: justify;
} }
.report-dialog-submit-text { #report-dialog .report-dialog-submit-text {
font-family: inherit; font-family: inherit;
font-size: 18px; font-size: 18px;
line-height: 20px; line-height: 1.2;
margin: 0px; margin: 0px;
text-align: center; text-align: center;
} }
.report-dialog-submit-view { #report-dialog .report-dialog-submit-view {
align-items: center; align-items: center;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
@ -232,6 +270,6 @@
margin-top: 16px; margin-top: 16px;
} }
.report-dialog-submit-view[hidden] { #report-dialog .report-dialog-submit-view[hidden] {
display: none; display: none;
} }