Files
formipay/node_modules/lighthouse/report/renderer/snippet-renderer.js
dwindown e8fbfb14c1 fix: prevent asset conflicts between React and Grid.js versions
Add coexistence checks to all enqueue methods to prevent loading
both React and Grid.js assets simultaneously.

Changes:
- ReactAdmin.php: Only enqueue React assets when ?react=1
- Init.php: Skip Grid.js when React active on admin pages
- Form.php, Coupon.php, Access.php: Restore classic assets when ?react=0
- Customer.php, Product.php, License.php: Add coexistence checks

Now the toggle between Classic and React versions works correctly.

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-18 17:02:14 +07:00

356 lines
12 KiB
JavaScript

/**
* @license Copyright 2019 The Lighthouse Authors. All Rights Reserved.
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
*/
/** @typedef {import('./details-renderer').DetailsRenderer} DetailsRenderer */
/** @typedef {import('./dom').DOM} DOM */
import {Util} from '../../shared/util.js';
import {Globals} from './report-globals.js';
/** @enum {number} */
const LineVisibility = {
/** Show regardless of whether the snippet is collapsed or expanded */
ALWAYS: 0,
WHEN_COLLAPSED: 1,
WHEN_EXPANDED: 2,
};
/** @enum {number} */
const LineContentType = {
/** A line of content */
CONTENT_NORMAL: 0,
/** A line of content that's emphasized by setting the CSS background color */
CONTENT_HIGHLIGHTED: 1,
/** Use when some lines are hidden, shows the "..." placeholder */
PLACEHOLDER: 2,
/** A message about a line of content or the snippet in general */
MESSAGE: 3,
};
/** @typedef {{
content: string;
lineNumber: string | number;
contentType: LineContentType;
truncated?: boolean;
visibility?: LineVisibility;
}} LineDetails */
const classNamesByContentType = {
[LineContentType.CONTENT_NORMAL]: ['lh-snippet__line--content'],
[LineContentType.CONTENT_HIGHLIGHTED]: [
'lh-snippet__line--content',
'lh-snippet__line--content-highlighted',
],
[LineContentType.PLACEHOLDER]: ['lh-snippet__line--placeholder'],
[LineContentType.MESSAGE]: ['lh-snippet__line--message'],
};
/**
* @param {LH.Audit.Details.SnippetValue['lines']} lines
* @param {number} lineNumber
* @return {{line?: LH.Audit.Details.SnippetValue['lines'][0], previousLine?: LH.Audit.Details.SnippetValue['lines'][0]}}
*/
function getLineAndPreviousLine(lines, lineNumber) {
return {
line: lines.find(l => l.lineNumber === lineNumber),
previousLine: lines.find(l => l.lineNumber === lineNumber - 1),
};
}
/**
* @param {LH.Audit.Details.SnippetValue["lineMessages"]} messages
* @param {number} lineNumber
*/
function getMessagesForLineNumber(messages, lineNumber) {
return messages.filter(h => h.lineNumber === lineNumber);
}
/**
* @param {LH.Audit.Details.SnippetValue} details
* @return {LH.Audit.Details.SnippetValue['lines']}
*/
function getLinesWhenCollapsed(details) {
const SURROUNDING_LINES_TO_SHOW_WHEN_COLLAPSED = 2;
return Util.filterRelevantLines(
details.lines,
details.lineMessages,
SURROUNDING_LINES_TO_SHOW_WHEN_COLLAPSED
);
}
/**
* Render snippet of text with line numbers and annotations.
* By default we only show a few lines around each annotation and the user
* can click "Expand snippet" to show more.
* Content lines with annotations are highlighted.
*/
export class SnippetRenderer {
/**
* @param {DOM} dom
* @param {LH.Audit.Details.SnippetValue} details
* @param {DetailsRenderer} detailsRenderer
* @param {function} toggleExpandedFn
* @return {DocumentFragment}
*/
static renderHeader(dom, details, detailsRenderer, toggleExpandedFn) {
const linesWhenCollapsed = getLinesWhenCollapsed(details);
const canExpand = linesWhenCollapsed.length < details.lines.length;
const header = dom.createComponent('snippetHeader');
dom.find('.lh-snippet__title', header).textContent = details.title;
const {
snippetCollapseButtonLabel,
snippetExpandButtonLabel,
} = Globals.strings;
dom.find(
'.lh-snippet__btn-label-collapse',
header
).textContent = snippetCollapseButtonLabel;
dom.find(
'.lh-snippet__btn-label-expand',
header
).textContent = snippetExpandButtonLabel;
const toggleExpandButton = dom.find('.lh-snippet__toggle-expand', header);
// If we're already showing all the available lines of the snippet, we don't need an
// expand/collapse button and can remove it from the DOM.
// If we leave the button in though, wire up the click listener to toggle visibility!
if (!canExpand) {
toggleExpandButton.remove();
} else {
toggleExpandButton.addEventListener('click', () => toggleExpandedFn());
}
// We only show the source node of the snippet in DevTools because then the user can
// access the full element detail. Just being able to see the outer HTML isn't very useful.
if (details.node && dom.isDevTools()) {
const nodeContainer = dom.find('.lh-snippet__node', header);
nodeContainer.append(detailsRenderer.renderNode(details.node));
}
return header;
}
/**
* Renders a line (text content, message, or placeholder) as a DOM element.
* @param {DOM} dom
* @param {DocumentFragment} tmpl
* @param {LineDetails} lineDetails
* @return {Element}
*/
static renderSnippetLine(
dom,
tmpl,
{content, lineNumber, truncated, contentType, visibility}
) {
const clonedTemplate = dom.createComponent('snippetLine');
const contentLine = dom.find('.lh-snippet__line', clonedTemplate);
const {classList} = contentLine;
classNamesByContentType[contentType].forEach(typeClass =>
classList.add(typeClass)
);
if (visibility === LineVisibility.WHEN_COLLAPSED) {
classList.add('lh-snippet__show-if-collapsed');
} else if (visibility === LineVisibility.WHEN_EXPANDED) {
classList.add('lh-snippet__show-if-expanded');
}
const lineContent = content + (truncated ? '…' : '');
const lineContentEl = dom.find('.lh-snippet__line code', contentLine);
if (contentType === LineContentType.MESSAGE) {
lineContentEl.append(dom.convertMarkdownLinkSnippets(lineContent));
} else {
lineContentEl.textContent = lineContent;
}
dom.find(
'.lh-snippet__line-number',
contentLine
).textContent = lineNumber.toString();
return contentLine;
}
/**
* @param {DOM} dom
* @param {DocumentFragment} tmpl
* @param {{message: string}} message
* @return {Element}
*/
static renderMessage(dom, tmpl, message) {
return SnippetRenderer.renderSnippetLine(dom, tmpl, {
lineNumber: ' ',
content: message.message,
contentType: LineContentType.MESSAGE,
});
}
/**
* @param {DOM} dom
* @param {DocumentFragment} tmpl
* @param {LineVisibility} visibility
* @return {Element}
*/
static renderOmittedLinesPlaceholder(dom, tmpl, visibility) {
return SnippetRenderer.renderSnippetLine(dom, tmpl, {
lineNumber: '…',
content: '',
visibility,
contentType: LineContentType.PLACEHOLDER,
});
}
/**
* @param {DOM} dom
* @param {DocumentFragment} tmpl
* @param {LH.Audit.Details.SnippetValue} details
* @return {DocumentFragment}
*/
static renderSnippetContent(dom, tmpl, details) {
const template = dom.createComponent('snippetContent');
const snippetEl = dom.find('.lh-snippet__snippet-inner', template);
// First render messages that don't belong to specific lines
details.generalMessages.forEach(m =>
snippetEl.append(SnippetRenderer.renderMessage(dom, tmpl, m))
);
// Then render the lines and their messages, as well as placeholders where lines are omitted
snippetEl.append(SnippetRenderer.renderSnippetLines(dom, tmpl, details));
return template;
}
/**
* @param {DOM} dom
* @param {DocumentFragment} tmpl
* @param {LH.Audit.Details.SnippetValue} details
* @return {DocumentFragment}
*/
static renderSnippetLines(dom, tmpl, details) {
const {lineMessages, generalMessages, lineCount, lines} = details;
const linesWhenCollapsed = getLinesWhenCollapsed(details);
const hasOnlyGeneralMessages =
generalMessages.length > 0 && lineMessages.length === 0;
const lineContainer = dom.createFragment();
// When a line is not shown in the collapsed state we try to see if we also need an
// omitted lines placeholder for the expanded state, rather than rendering two separate
// placeholders.
let hasPendingOmittedLinesPlaceholderForCollapsedState = false;
for (let lineNumber = 1; lineNumber <= lineCount; lineNumber++) {
const {line, previousLine} = getLineAndPreviousLine(lines, lineNumber);
const {
line: lineWhenCollapsed,
previousLine: previousLineWhenCollapsed,
} = getLineAndPreviousLine(linesWhenCollapsed, lineNumber);
const showLineWhenCollapsed = !!lineWhenCollapsed;
const showPreviousLineWhenCollapsed = !!previousLineWhenCollapsed;
// If we went from showing lines in the collapsed state to not showing them
// we need to render a placeholder
if (showPreviousLineWhenCollapsed && !showLineWhenCollapsed) {
hasPendingOmittedLinesPlaceholderForCollapsedState = true;
}
// If we are back to lines being visible in the collapsed and the placeholder
// hasn't been rendered yet then render it now
if (
showLineWhenCollapsed &&
hasPendingOmittedLinesPlaceholderForCollapsedState
) {
lineContainer.append(
SnippetRenderer.renderOmittedLinesPlaceholder(
dom,
tmpl,
LineVisibility.WHEN_COLLAPSED
)
);
hasPendingOmittedLinesPlaceholderForCollapsedState = false;
}
// Render omitted lines placeholder if we have not already rendered one for this gap
const isFirstOmittedLineWhenExpanded = !line && !!previousLine;
const isFirstLineOverallAndIsOmittedWhenExpanded =
!line && lineNumber === 1;
if (
isFirstOmittedLineWhenExpanded ||
isFirstLineOverallAndIsOmittedWhenExpanded
) {
// In the collapsed state we don't show omitted lines placeholders around
// the edges of the snippet
const hasRenderedAllLinesVisibleWhenCollapsed = !linesWhenCollapsed.some(
l => l.lineNumber > lineNumber
);
const onlyShowWhenExpanded =
hasRenderedAllLinesVisibleWhenCollapsed || lineNumber === 1;
lineContainer.append(
SnippetRenderer.renderOmittedLinesPlaceholder(
dom,
tmpl,
onlyShowWhenExpanded
? LineVisibility.WHEN_EXPANDED
: LineVisibility.ALWAYS
)
);
hasPendingOmittedLinesPlaceholderForCollapsedState = false;
}
if (!line) {
// Can't render the line if we don't know its content (instead we've rendered a placeholder)
continue;
}
// Now render the line and any messages
const messages = getMessagesForLineNumber(lineMessages, lineNumber);
const highlightLine = messages.length > 0 || hasOnlyGeneralMessages;
const contentLineDetails = Object.assign({}, line, {
contentType: highlightLine
? LineContentType.CONTENT_HIGHLIGHTED
: LineContentType.CONTENT_NORMAL,
visibility: lineWhenCollapsed
? LineVisibility.ALWAYS
: LineVisibility.WHEN_EXPANDED,
});
lineContainer.append(
SnippetRenderer.renderSnippetLine(dom, tmpl, contentLineDetails)
);
messages.forEach(message => {
lineContainer.append(SnippetRenderer.renderMessage(dom, tmpl, message));
});
}
return lineContainer;
}
/**
* @param {DOM} dom
* @param {LH.Audit.Details.SnippetValue} details
* @param {DetailsRenderer} detailsRenderer
* @return {!Element}
*/
static render(dom, details, detailsRenderer) {
const tmpl = dom.createComponent('snippet');
const snippetEl = dom.find('.lh-snippet', tmpl);
const header = SnippetRenderer.renderHeader(
dom,
details,
detailsRenderer,
() => snippetEl.classList.toggle('lh-snippet--expanded')
);
const content = SnippetRenderer.renderSnippetContent(dom, tmpl, details);
snippetEl.append(header, content);
return snippetEl;
}
}