diff --git a/assets/css/amp-validation-error-taxonomy.css b/assets/css/amp-validation-error-taxonomy.css new file mode 100644 index 00000000000..91192865397 --- /dev/null +++ b/assets/css/amp-validation-error-taxonomy.css @@ -0,0 +1,138 @@ +#col-left { + display: none; +} +#col-right { + float: none; + width: auto; +} + +/* Improve column widths */ +td.column-details pre, +td.column-sources pre { + overflow: auto; +} + +th.column-created_date_gmt, +th.column-error_type { + width: 15%; +} + +th.column-status { + width: 10%; +} + +.fixed th.column-posts { + width: 10%; +} + +/* Details column */ +.details-attributes__summary { + display: flex; + justify-content: space-between; + align-items: center; +} + +details[open] .details-attributes__summary { + font-weight: 600; + margin-bottom: 15px; +} + +.details-attributes__summary::-webkit-details-marker { + display: none; +} + +.details-attributes__summary::after { + order: 99; + width: 12px; + height: 12px; + background-image: url("../images/down-triangle.svg"); + background-size: cover; + background-position: center; + content: ""; +} + +details[open] .details-attributes__summary::after { + transform: rotate(180deg); +} + +.details-attributes__title, +.details-attributes__attr { + color: #dc3232; +} + +.details-attributes__list { + margin-top: 0; + padding-left: 45px; + list-style-type: disc; +} + +.details-attributes__list li { + word-break: break-all; +} + +.details-attributes__value { + color: #00a0d2; +} + +/* Error details toggle button */ +.manage-column.column-details { + display: flex; + justify-content: space-between; + align-items: center; +} + +.error-details-toggle { + display: flex; + flex-direction: column; + height: 12px; + margin-right: 10px; + padding: 0; + background: none; + border: none; + cursor: pointer; +} + +.error-details-toggle.is-open { + transform: rotate(180deg); +} + +.error-details-toggle::before, +.error-details-toggle::after { + width: 12px; + height: 12px; + background-image: url("../images/down-triangle.svg"); + background-size: cover; + background-position: center; + content: ""; +} + +/* Status text icons */ +.status-text { + display: flex; + align-items: center; +} + +.status-text::before { + margin-right: 10px; + background-size: 20px 20px; + height: 20px; + width: 20px; + content: ""; +} + +.status-text.sanitized::before { + background-image: url( '../images/amp-logo-icon.svg' ); +} + +.status-text.new::before { + background-image: url( '../images/baseline-error.svg' ); +} + +.status-text.accepted::before { + background-image: url( '../images/baseline-check-circle-green.svg' ); +} + +.status-text.rejected::before { + background-image: url( '../images/baseline-error-blue.svg' ); +} + diff --git a/assets/images/baseline-check-circle-green.svg b/assets/images/baseline-check-circle-green.svg new file mode 100644 index 00000000000..7e015acf334 --- /dev/null +++ b/assets/images/baseline-check-circle-green.svg @@ -0,0 +1,4 @@ + + + + diff --git a/assets/images/down-triangle.svg b/assets/images/down-triangle.svg new file mode 100644 index 00000000000..0baeab7fc11 --- /dev/null +++ b/assets/images/down-triangle.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/src/amp-validation-error-detail-toggle.js b/assets/src/amp-validation-error-detail-toggle.js new file mode 100644 index 00000000000..85275c20b06 --- /dev/null +++ b/assets/src/amp-validation-error-detail-toggle.js @@ -0,0 +1,60 @@ +/** + * WordPress dependencies + */ +import domReady from '@wordpress/dom-ready'; + +/** + * Localized data + */ +import { btnAriaLabel } from 'amp-validation-i18n'; + +const OPEN_CLASS = 'is-open'; + +/** + * Adds detail toggle buttons to the header and footer rows of the validation error "details" column. + * The buttons are added via JS because there's no easy way to append them to the heading of a sortable + * table column via backend code. + */ +function addToggleButtons() { + [ ...document.querySelectorAll( 'th.column-details.manage-column' ) ].forEach( th => { + const button = document.createElement( 'button' ); + button.setAttribute( 'aria-label', btnAriaLabel ); + button.setAttribute( 'type', 'button' ); + button.setAttribute( 'class', 'error-details-toggle' ); + th.appendChild( button ); + } ); +} + +/** + * Adds a listener toggling all details in the error type taxonomy details column. + */ +function addToggleListener() { + let open = false; + + const details = [ ...document.querySelectorAll( '.column-details details' ) ]; + const toggleButtons = [ ...document.querySelectorAll( 'button.error-details-toggle' ) ]; + const onButtonClick = () => { + open = ! open; + toggleButtons.forEach( btn => { + btn.classList.toggle( OPEN_CLASS ); + } ); + details.forEach( detail => { + if ( open ) { + detail.setAttribute( 'open', true ); + } else { + detail.removeAttribute( 'open' ); + } + } ); + }; + + window.addEventListener( 'click', event => { + if ( toggleButtons.includes( event.target ) ) { + onButtonClick(); + } + } ); +} + +domReady( () => { + addToggleButtons(); + addToggleListener(); +} ); diff --git a/dev-lib b/dev-lib index 213ab6905ac..32430f45c03 160000 --- a/dev-lib +++ b/dev-lib @@ -1 +1 @@ -Subproject commit 213ab6905acef0d7655390f1487315c184f130af +Subproject commit 32430f45c03ce40b3755f7c2c0b03c8857154d59 diff --git a/includes/sanitizers/class-amp-style-sanitizer.php b/includes/sanitizers/class-amp-style-sanitizer.php index f6d2329aef7..9e617ecd745 100644 --- a/includes/sanitizers/class-amp-style-sanitizer.php +++ b/includes/sanitizers/class-amp-style-sanitizer.php @@ -32,6 +32,13 @@ class AMP_Style_Sanitizer extends AMP_Base_Sanitizer { */ const TREE_SHAKING_ERROR_CODE = 'removed_unused_css_rules'; + /** + * Error code for illegal at-rule. + * + * @var string + */ + const ILLEGAL_AT_RULE_ERROR_CODE = 'illegal_css_at_rule'; + /** * Inline style selector's specificity multiplier, i.e. used to generate the number of ':not(#_)' placeholders. * @@ -233,7 +240,7 @@ public static function get_css_parser_validation_error_codes() { return array( 'css_parse_error', 'excessive_css', - 'illegal_css_at_rule', + self::ILLEGAL_AT_RULE_ERROR_CODE, 'illegal_css_important', 'illegal_css_property', self::TREE_SHAKING_ERROR_CODE, @@ -1243,7 +1250,7 @@ private function process_css_list( CSSList $css_list, $options ) { } elseif ( $css_item instanceof AtRuleBlockList ) { if ( ! in_array( $css_item->atRuleName(), $options['allowed_at_rules'], true ) ) { $error = array( - 'code' => 'illegal_css_at_rule', + 'code' => self::ILLEGAL_AT_RULE_ERROR_CODE, 'at_rule' => $css_item->atRuleName(), 'type' => AMP_Validation_Error_Taxonomy::CSS_ERROR_TYPE, ); @@ -1264,7 +1271,7 @@ private function process_css_list( CSSList $css_list, $options ) { } elseif ( $css_item instanceof AtRuleSet ) { if ( ! in_array( $css_item->atRuleName(), $options['allowed_at_rules'], true ) ) { $error = array( - 'code' => 'illegal_css_at_rule', + 'code' => self::ILLEGAL_AT_RULE_ERROR_CODE, 'at_rule' => $css_item->atRuleName(), 'type' => AMP_Validation_Error_Taxonomy::CSS_ERROR_TYPE, ); @@ -1281,7 +1288,7 @@ private function process_css_list( CSSList $css_list, $options ) { } elseif ( $css_item instanceof KeyFrame ) { if ( ! in_array( 'keyframes', $options['allowed_at_rules'], true ) ) { $error = array( - 'code' => 'illegal_css_at_rule', + 'code' => self::ILLEGAL_AT_RULE_ERROR_CODE, 'at_rule' => $css_item->atRuleName(), 'type' => AMP_Validation_Error_Taxonomy::CSS_ERROR_TYPE, ); @@ -1297,7 +1304,7 @@ private function process_css_list( CSSList $css_list, $options ) { } } elseif ( $css_item instanceof AtRule ) { $error = array( - 'code' => 'illegal_css_at_rule', + 'code' => self::ILLEGAL_AT_RULE_ERROR_CODE, 'at_rule' => $css_item->atRuleName(), 'type' => AMP_Validation_Error_Taxonomy::CSS_ERROR_TYPE, ); diff --git a/includes/validation/class-amp-validation-error-taxonomy.php b/includes/validation/class-amp-validation-error-taxonomy.php index f02c25ea56e..c46ce83c5b4 100644 --- a/includes/validation/class-amp-validation-error-taxonomy.php +++ b/includes/validation/class-amp-validation-error-taxonomy.php @@ -68,6 +68,20 @@ class AMP_Validation_Error_Taxonomy { */ const VALIDATION_ERROR_TYPE_QUERY_VAR = 'amp_validation_error_type'; + /** + * Query var used for ordering list by node name. + * + * @var string + */ + const VALIDATION_DETAILS_NODE_NAME_QUERY_VAR = 'amp_validation_node_name'; + + /** + * Query var used for ordering list by error code. + * + * @var string + */ + const VALIDATION_DETAILS_ERROR_CODE_QUERY_VAR = 'amp_validation_code'; + /** * The