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>
This commit is contained in:
dwindown
2026-04-18 17:02:14 +07:00
parent bd9cdac02e
commit e8fbfb14c1
74973 changed files with 6658406 additions and 71 deletions

View File

@@ -0,0 +1,21 @@
'use strict';
/**
* @typedef {import('stylelint').Severity} Severity
*
* @param {Severity} severity
* @param {Record<Severity, number>} counts
* @returns {void}
*/
module.exports = function calcSeverityCounts(severity, counts) {
switch (severity) {
case 'error':
counts.error += 1;
break;
case 'warning':
counts.warning += 1;
break;
default:
throw new Error(`Unknown severity: "${severity}"`);
}
};

View File

@@ -0,0 +1,23 @@
'use strict';
const preprocessWarnings = require('./preprocessWarnings');
/**
* @type {import('stylelint').Formatter}
*/
module.exports = function compactFormatter(results) {
return results
.flatMap((result) => {
const { warnings } = preprocessWarnings(result);
return warnings.map(
(warning) =>
`${result.source}: ` +
`line ${warning.line}, ` +
`col ${warning.column}, ` +
`${warning.severity} - ` +
`${warning.text}`,
);
})
.join('\n');
};

View File

@@ -0,0 +1,41 @@
'use strict';
const preprocessWarnings = require('./preprocessWarnings');
/**
* @see https://docs.github.com/en/actions/using-workflows/workflow-commands-for-github-actions
*
* @type {import('stylelint').Formatter}
*/
module.exports = function githubFormatter(results, returnValue) {
const title = 'Stylelint problem';
const metadata = returnValue.ruleMetadata;
return results
.flatMap((result) => {
const { source, warnings } = preprocessWarnings(result);
return warnings.map(({ line, column, endLine, endColumn, text, severity, rule }) => {
const msg = buildMessage(text, metadata[rule]);
return endLine === undefined
? `::${severity} file=${source},line=${line},col=${column},title=${title}::${msg}`
: `::${severity} file=${source},line=${line},col=${column},endLine=${endLine},endColumn=${endColumn},title=${title}::${msg}`;
});
})
.join('\n');
};
/**
* @param {string} msg
* @param {Partial<import('stylelint').RuleMeta> | undefined} metadata
* @returns {string}
*/
function buildMessage(msg, metadata) {
if (!metadata) return msg;
const url = metadata.url ? ` - ${metadata.url}` : '';
const fixable = metadata.fixable ? ' [maybe fixable]' : '';
return `${msg}${fixable}${url}`;
}

16
node_modules/stylelint/lib/formatters/index.js generated vendored Normal file
View File

@@ -0,0 +1,16 @@
'use strict';
const importLazy = require('import-lazy');
/** @type {typeof import('stylelint').formatters} */
const formatters = {
compact: importLazy(() => require('./compactFormatter'))(),
github: importLazy(() => require('./githubFormatter'))(),
json: importLazy(() => require('./jsonFormatter'))(),
string: importLazy(() => require('./stringFormatter'))(),
tap: importLazy(() => require('./tapFormatter'))(),
unix: importLazy(() => require('./unixFormatter'))(),
verbose: importLazy(() => require('./verboseFormatter'))(),
};
module.exports = formatters;

20
node_modules/stylelint/lib/formatters/jsonFormatter.js generated vendored Normal file
View File

@@ -0,0 +1,20 @@
'use strict';
/**
* Omit any properties starting with `_`, which are fake-private
*
* @type {import('stylelint').Formatter}
*/
module.exports = function jsonFormatter(results) {
const cleanedResults = results.map((result) =>
Object.entries(result)
.filter(([key]) => !key.startsWith('_'))
.reduce((/** @type {{ [key: string]: any }} */ obj, [key, value]) => {
obj[key] = value;
return obj;
}, {}),
);
return JSON.stringify(cleanedResults);
};

View File

@@ -0,0 +1,74 @@
'use strict';
/** @typedef {import('stylelint').LintResult} LintResult */
/** @typedef {LintResult['parseErrors'][0]} ParseError */
/** @typedef {LintResult['warnings'][0]} Warning */
/** @typedef {Warning['severity']} Severity */
/**
* Preprocess warnings in a given lint result.
* Note that this function has a side-effect.
*
* @param {LintResult} result
* @returns {LintResult}
*/
module.exports = function preprocessWarnings(result) {
for (const error of result.parseErrors || []) {
result.warnings.push(parseErrorToWarning(error));
}
for (const warning of result.warnings) {
warning.severity = normalizeSeverity(warning);
}
result.warnings.sort(byLocationOrder);
return result;
};
/**
* @param {ParseError} error
* @returns {Warning}
*/
function parseErrorToWarning(error) {
return {
line: error.line,
column: error.column,
rule: error.stylelintType,
severity: 'error',
text: `${error.text} (${error.stylelintType})`,
};
}
/**
* @param {Warning} warning
* @returns {Severity}
*/
function normalizeSeverity(warning) {
// NOTE: Plugins may add a warning without severity, for example,
// by directly using the PostCSS `Result#warn()` API.
return warning.severity || 'error';
}
/**
* @param {Warning} a
* @param {Warning} b
* @returns {number}
*/
function byLocationOrder(a, b) {
// positionless first
if (!a.line && b.line) return -1;
// positionless first
if (a.line && !b.line) return 1;
if (a.line < b.line) return -1;
if (a.line > b.line) return 1;
if (a.column < b.column) return -1;
if (a.column > b.column) return 1;
return 0;
}

View File

@@ -0,0 +1,271 @@
'use strict';
const path = require('path');
const stringWidth = require('string-width');
const table = require('table');
const { yellow, dim, underline, blue, red, green } = require('picocolors');
const calcSeverityCounts = require('./calcSeverityCounts');
const pluralize = require('../utils/pluralize');
const { assertNumber } = require('../utils/validateTypes');
const preprocessWarnings = require('./preprocessWarnings');
const terminalLink = require('./terminalLink');
const MARGIN_WIDTHS = 9;
/**
* @param {string} s
* @returns {string}
*/
function nope(s) {
return s;
}
const levelColors = {
info: blue,
warning: yellow,
error: red,
success: nope,
};
const symbols = {
info: blue(''),
warning: yellow('⚠'),
error: red('✖'),
success: green('✔'),
};
/**
* @param {import('stylelint').LintResult[]} results
* @returns {string}
*/
function deprecationsFormatter(results) {
const allDeprecationWarnings = results.flatMap((result) => result.deprecations || []);
if (allDeprecationWarnings.length === 0) {
return '';
}
const seenText = new Set();
return allDeprecationWarnings.reduce((output, warning) => {
if (seenText.has(warning.text)) return output;
seenText.add(warning.text);
output += yellow('Deprecation Warning: ');
output += warning.text;
if (warning.reference) {
output += dim(' See: ');
output += dim(underline(warning.reference));
}
return `${output}\n`;
}, '\n');
}
/**
* @param {import('stylelint').LintResult[]} results
* @return {string}
*/
function invalidOptionsFormatter(results) {
const allInvalidOptionWarnings = results.flatMap((result) =>
(result.invalidOptionWarnings || []).map((warning) => warning.text),
);
const uniqueInvalidOptionWarnings = [...new Set(allInvalidOptionWarnings)];
return uniqueInvalidOptionWarnings.reduce((output, warning) => {
output += red('Invalid Option: ');
output += warning;
return `${output}\n`;
}, '\n');
}
/**
* @param {string} fromValue
* @param {string} cwd
* @return {string}
*/
function logFrom(fromValue, cwd) {
if (fromValue.startsWith('<')) {
return underline(fromValue);
}
const filePath = path.relative(cwd, fromValue).split(path.sep).join('/');
return terminalLink(filePath, `file://${fromValue}`);
}
/**
* @param {{[k: number]: number}} columnWidths
* @return {number}
*/
function getMessageWidth(columnWidths) {
const width = columnWidths[3];
assertNumber(width);
if (!process.stdout.isTTY) {
return width;
}
const availableWidth = process.stdout.columns < 80 ? 80 : process.stdout.columns;
const fullWidth = Object.values(columnWidths).reduce((a, b) => a + b);
// If there is no reason to wrap the text, we won't align the last column to the right
if (availableWidth > fullWidth + MARGIN_WIDTHS) {
return width;
}
return availableWidth - (fullWidth - width + MARGIN_WIDTHS);
}
/**
* @param {import('stylelint').Warning[]} messages
* @param {string} source
* @param {string} cwd
* @return {string}
*/
function formatter(messages, source, cwd) {
if (messages.length === 0) return '';
/**
* Create a list of column widths, needed to calculate
* the size of the message column and if needed wrap it.
* @type {{[k: string]: number}}
*/
const columnWidths = { 0: 1, 1: 1, 2: 1, 3: 1, 4: 1 };
/**
* @param {[string, string, string, string, string]} columns
* @return {[string, string, string, string, string]}
*/
function calculateWidths(columns) {
for (const [key, value] of Object.entries(columns)) {
const normalisedValue = value ? value.toString() : value;
const width = columnWidths[key];
assertNumber(width);
columnWidths[key] = Math.max(width, stringWidth(normalisedValue));
}
return columns;
}
let output = '\n';
if (source) {
output += `${logFrom(source, cwd)}\n`;
}
/**
* @param {import('stylelint').Warning} message
* @return {string}
*/
function formatMessageText(message) {
let result = message.text;
result = result
// Remove all control characters (newline, tab and etc)
.replace(/[\u0001-\u001A]+/g, ' ') // eslint-disable-line no-control-regex
.replace(/\.$/, '');
const ruleString = ` (${message.rule})`;
if (result.endsWith(ruleString)) {
result = result.slice(0, result.lastIndexOf(ruleString));
}
return result;
}
const cleanedMessages = messages.map((message) => {
const { line, column, severity } = message;
/**
* @type {[string, string, string, string, string]}
*/
const row = [
line ? line.toString() : '',
column ? column.toString() : '',
symbols[severity] ? levelColors[severity](symbols[severity]) : severity,
formatMessageText(message),
dim(message.rule || ''),
];
calculateWidths(row);
return row;
});
output += table
.table(cleanedMessages, {
border: table.getBorderCharacters('void'),
columns: {
0: { alignment: 'right', width: columnWidths[0], paddingRight: 0 },
1: { alignment: 'left', width: columnWidths[1] },
2: { alignment: 'center', width: columnWidths[2] },
3: {
alignment: 'left',
width: getMessageWidth(columnWidths),
wrapWord: getMessageWidth(columnWidths) > 1,
},
4: { alignment: 'left', width: columnWidths[4], paddingRight: 0 },
},
drawHorizontalLine: () => false,
})
.split('\n')
.map((el) => el.replace(/(\d+)\s+(\d+)/, (_m, p1, p2) => dim(`${p1}:${p2}`)).trimEnd())
.join('\n');
return output;
}
/**
* @type {import('stylelint').Formatter}
*/
module.exports = function stringFormatter(results, returnValue) {
let output = invalidOptionsFormatter(results);
output += deprecationsFormatter(results);
const counts = { error: 0, warning: 0 };
output = results.reduce((accum, result) => {
preprocessWarnings(result);
accum += formatter(
result.warnings,
result.source || '',
(returnValue && returnValue.cwd) || process.cwd(),
);
for (const warning of result.warnings) {
calcSeverityCounts(warning.severity, counts);
}
return accum;
}, output);
// Ensure consistent padding
output = output.trim();
if (output !== '') {
output = `\n${output}\n\n`;
const errorCount = counts.error;
const warningCount = counts.warning;
const total = errorCount + warningCount;
if (total > 0) {
const error = red(`${errorCount} ${pluralize('error', errorCount)}`);
const warning = yellow(`${warningCount} ${pluralize('warning', warningCount)}`);
const tally = `${total} ${pluralize('problem', total)} (${error}, ${warning})`;
output += `${tally}\n\n`;
}
}
return output;
};

52
node_modules/stylelint/lib/formatters/tapFormatter.js generated vendored Normal file
View File

@@ -0,0 +1,52 @@
'use strict';
const preprocessWarnings = require('./preprocessWarnings');
/**
* @type {import('stylelint').Formatter}
*/
module.exports = function tapFormatter(results) {
const lines = [`TAP version 13\n1..${results.length}`];
for (const [index, result] of results.entries()) {
preprocessWarnings(result);
lines.push(
`${result.errored ? 'not ok' : 'ok'} ${index + 1} - ${result.ignored ? 'ignored ' : ''}${
result.source
}`,
);
if (result.warnings.length > 0) {
lines.push('---', 'messages:');
for (const warning of result.warnings) {
lines.push(
` - message: "${warning.text}"`,
` severity: ${warning.severity}`,
` data:`,
` line: ${warning.line}`,
` column: ${warning.column}`,
);
if (typeof warning.endLine === 'number') {
lines.push(` endLine: ${warning.endLine}`);
}
if (typeof warning.endColumn === 'number') {
lines.push(` endColumn: ${warning.endColumn}`);
}
if (typeof warning.rule === 'string') {
lines.push(` ruleId: ${warning.rule}`);
}
}
lines.push('---');
}
}
lines.push('');
return lines.join('\n');
};

23
node_modules/stylelint/lib/formatters/terminalLink.js generated vendored Normal file
View File

@@ -0,0 +1,23 @@
'use strict';
const supportsHyperlinks = require('supports-hyperlinks');
// ANSI escapes
const OSC = '\u001B]';
const BEL = '\u0007';
const SEP = ';';
/**
* @see https://gist.github.com/egmontkob/eb114294efbcd5adb1944c9f3cb5feda
*
* @param {string} text
* @param {string} url
* @returns {string}
*/
module.exports = function terminalLink(text, url) {
if (supportsHyperlinks.stdout) {
return [OSC, '8', SEP, SEP, url, BEL, text, OSC, '8', SEP, SEP, BEL].join('');
}
return text;
};

34
node_modules/stylelint/lib/formatters/unixFormatter.js generated vendored Normal file
View File

@@ -0,0 +1,34 @@
'use strict';
const calcSeverityCounts = require('./calcSeverityCounts');
const pluralize = require('../utils/pluralize');
const preprocessWarnings = require('./preprocessWarnings');
/**
* @type {import('stylelint').Formatter}
*/
module.exports = function unixFormatter(results) {
const counts = { error: 0, warning: 0 };
const lines = results.flatMap((result) => {
preprocessWarnings(result);
return result.warnings.map((warning) => {
calcSeverityCounts(warning.severity, counts);
return (
`${result.source}:${warning.line}:${warning.column}: ` +
`${warning.text} [${warning.severity}]`
);
});
});
const total = lines.length;
let output = lines.join('\n');
if (total > 0) {
output += `\n\n${total} ${pluralize('problem', total)}`;
output += ` (${counts.error} ${pluralize('error', counts.error)}`;
output += `, ${counts.warning} ${pluralize('warning', counts.warning)})\n`;
}
return output;
};

View File

@@ -0,0 +1,144 @@
'use strict';
const { underline, red, yellow, dim, green } = require('picocolors');
const pluralize = require('../utils/pluralize');
const stringFormatter = require('./stringFormatter');
const terminalLink = require('./terminalLink');
/** @typedef {import('stylelint').Formatter} Formatter */
/** @typedef {import('stylelint').LintResult} LintResult */
/** @typedef {import('stylelint').Warning} Warning */
/** @typedef {import('stylelint').Severity} Severity */
/** @typedef {import('stylelint').RuleMeta} RuleMeta */
/**
* @type {Formatter}
*/
module.exports = function verboseFormatter(results, returnValue) {
let output = stringFormatter(results, returnValue);
if (output === '') {
output = '\n';
}
const ignoredCount = results.filter((result) => result.ignored).length;
const checkedDisplay = ignoredCount
? `${results.length - ignoredCount} of ${results.length}`
: results.length;
output += underline(`${checkedDisplay} ${pluralize('source', results.length)} checked\n`);
for (const result of results) {
let formatting = green;
if (result.errored) {
formatting = red;
} else if (result.warnings.length) {
formatting = yellow;
} else if (result.ignored) {
formatting = dim;
}
let sourceText = fileLink(result.source);
if (result.ignored) {
sourceText += ' (ignored)';
}
output += formatting(` ${sourceText}\n`);
}
const warnings = results.flatMap((r) => r.warnings);
if (warnings.length === 0) {
output += '\n0 problems found\n';
} else {
const warningsBySeverity = groupBy(warnings, (w) => w.severity);
let fixableProblemsFound = false;
/**
* @param {Severity} severity
*/
const printProblems = (severity) => {
const problems = warningsBySeverity[severity];
if (problems === undefined) return;
output += '\n';
output += underline(`${problems.length} ${pluralize(severity, problems.length)} found\n`);
const problemsByRule = groupBy(problems, (w) => w.rule);
const metadata = returnValue.ruleMetadata;
for (const [rule, list] of Object.entries(problemsByRule)) {
const meta = metadata[rule];
const fixable = meta && meta.fixable ? ' (maybe fixable)' : '';
output += dim(` ${ruleLink(rule, meta)}: ${list.length}${fixable}\n`);
if (!fixableProblemsFound && meta && meta.fixable) {
fixableProblemsFound = true;
}
}
};
printProblems('error');
printProblems('warning');
if (fixableProblemsFound) {
output += yellow('\nYou may fix some problems with the "--fix" option.\n');
}
}
return `${output}\n`;
};
/**
* @template {string} K
* @param {Warning[]} array
* @param {(w: Warning) => K} keyFn
* @returns {Record<K, Warning[]>}
*/
function groupBy(array, keyFn) {
/** @type {Record<string, Warning[]>} */
const result = {};
for (const item of array) {
const key = keyFn(item);
let warnings = result[key];
if (warnings === undefined) {
result[key] = warnings = [];
}
warnings.push(item);
}
return result;
}
/**
* @param {string | undefined} source
* @returns {string}
*/
function fileLink(source) {
if (!source || source.startsWith('<')) {
return `${source}`;
}
return terminalLink(source, `file://${source}`);
}
/**
* @param {string} rule
* @param {Partial<RuleMeta> | undefined} metadata
* @returns {string}
*/
function ruleLink(rule, metadata) {
if (metadata && metadata.url) {
return terminalLink(rule, metadata.url);
}
return rule;
}