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

3
node_modules/highlight-words-core/src/index.js generated vendored Normal file
View File

@@ -0,0 +1,3 @@
// @flow
export { combineChunks, fillInChunks, findAll, findChunks } from './utils'

174
node_modules/highlight-words-core/src/utils.js generated vendored Normal file
View File

@@ -0,0 +1,174 @@
// @flow
export type Chunk = {|
highlight: boolean,
start: number,
end: number,
|};
/**
* Creates an array of chunk objects representing both higlightable and non highlightable pieces of text that match each search word.
* @return Array of "chunks" (where a Chunk is { start:number, end:number, highlight:boolean })
*/
export const findAll = ({
autoEscape,
caseSensitive = false,
findChunks = defaultFindChunks,
sanitize,
searchWords,
textToHighlight
}: {
autoEscape?: boolean,
caseSensitive?: boolean,
findChunks?: typeof defaultFindChunks,
sanitize?: typeof defaultSanitize,
searchWords: Array<string>,
textToHighlight: string,
}): Array<Chunk> => (
fillInChunks({
chunksToHighlight: combineChunks({
chunks: findChunks({
autoEscape,
caseSensitive,
sanitize,
searchWords,
textToHighlight
})
}),
totalLength: textToHighlight ? textToHighlight.length : 0
})
)
/**
* Takes an array of {start:number, end:number} objects and combines chunks that overlap into single chunks.
* @return {start:number, end:number}[]
*/
export const combineChunks = ({
chunks
}: {
chunks: Array<Chunk>,
}): Array<Chunk> => {
chunks = chunks
.sort((first, second) => first.start - second.start)
.reduce((processedChunks, nextChunk) => {
// First chunk just goes straight in the array...
if (processedChunks.length === 0) {
return [nextChunk]
} else {
// ... subsequent chunks get checked to see if they overlap...
const prevChunk = processedChunks.pop()
if (nextChunk.start < prevChunk.end) {
// It may be the case that prevChunk completely surrounds nextChunk, so take the
// largest of the end indexes.
const endIndex = Math.max(prevChunk.end, nextChunk.end)
processedChunks.push({highlight: false, start: prevChunk.start, end: endIndex})
} else {
processedChunks.push(prevChunk, nextChunk)
}
return processedChunks
}
}, [])
return chunks
}
/**
* Examine text for any matches.
* If we find matches, add them to the returned array as a "chunk" object ({start:number, end:number}).
* @return {start:number, end:number}[]
*/
const defaultFindChunks = ({
autoEscape,
caseSensitive,
sanitize = defaultSanitize,
searchWords,
textToHighlight
}: {
autoEscape?: boolean,
caseSensitive?: boolean,
sanitize?: typeof defaultSanitize,
searchWords: Array<string>,
textToHighlight: string,
}): Array<Chunk> => {
textToHighlight = sanitize(textToHighlight)
return searchWords
.filter(searchWord => searchWord) // Remove empty words
.reduce((chunks, searchWord) => {
searchWord = sanitize(searchWord)
if (autoEscape) {
searchWord = escapeRegExpFn(searchWord)
}
const regex = new RegExp(searchWord, caseSensitive ? 'g' : 'gi')
let match
while ((match = regex.exec(textToHighlight))) {
let start = match.index
let end = regex.lastIndex
// We do not return zero-length matches
if (end > start) {
chunks.push({highlight: false, start, end})
}
// Prevent browsers like Firefox from getting stuck in an infinite loop
// See http://www.regexguru.com/2008/04/watch-out-for-zero-length-matches/
if (match.index === regex.lastIndex) {
regex.lastIndex++
}
}
return chunks
}, [])
}
// Allow the findChunks to be overridden in findAll,
// but for backwards compatibility we export as the old name
export {defaultFindChunks as findChunks}
/**
* Given a set of chunks to highlight, create an additional set of chunks
* to represent the bits of text between the highlighted text.
* @param chunksToHighlight {start:number, end:number}[]
* @param totalLength number
* @return {start:number, end:number, highlight:boolean}[]
*/
export const fillInChunks = ({
chunksToHighlight,
totalLength
}: {
chunksToHighlight: Array<Chunk>,
totalLength: number,
}): Array<Chunk> => {
const allChunks = []
const append = (start, end, highlight) => {
if (end - start > 0) {
allChunks.push({
start,
end,
highlight
})
}
}
if (chunksToHighlight.length === 0) {
append(0, totalLength, false)
} else {
let lastIndex = 0
chunksToHighlight.forEach((chunk) => {
append(lastIndex, chunk.start, false)
append(chunk.start, chunk.end, true)
lastIndex = chunk.end
})
append(lastIndex, totalLength, false)
}
return allChunks
}
function defaultSanitize (string: string): string {
return string
}
function escapeRegExpFn (string: string): string {
return string.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, '\\$&')
}

149
node_modules/highlight-words-core/src/utils.test.js generated vendored Normal file
View File

@@ -0,0 +1,149 @@
import * as Chunks from './utils.js'
import expect from 'expect.js'
import latinize from 'latinize'
describe('utils', () => {
// Positions: 01234567890123456789012345678901234567
const TEXT = 'This is a string with words to search.'
it('should handle empty or null textToHighlight', () => {
let result = Chunks.findAll({
searchWords: ['search'],
textToHighlight: ''
})
expect(result.length).to.equal(0)
result = Chunks.findAll({
searchWords: ['search']
})
expect(result.length).to.equal(0)
})
it('should highlight all occurrences of a word, regardless of capitalization', () => {
const rawChunks = Chunks.findChunks({
searchWords: ['th'],
textToHighlight: TEXT
})
expect(rawChunks).to.eql([
{start: 0, end: 2, highlight: false},
{start: 19, end: 21, highlight: false}
])
})
it('should highlight words that partially overlap', () => {
const combinedChunks = Chunks.combineChunks({
chunks: Chunks.findChunks({
searchWords: ['thi', 'is'],
textToHighlight: TEXT
})
})
expect(combinedChunks).to.eql([
{start: 0, end: 4, highlight: false},
{start: 5, end: 7, highlight: false}
])
})
it('should combine into the minimum number of marked and unmarked chunks', () => {
const filledInChunks = Chunks.findAll({
searchWords: ['thi', 'is'],
textToHighlight: TEXT
})
expect(filledInChunks).to.eql([
{start: 0, end: 4, highlight: true},
{start: 4, end: 5, highlight: false},
{start: 5, end: 7, highlight: true},
{start: 7, end: 38, highlight: false}
])
})
it('should handle unclosed parentheses when autoEscape prop is truthy', () => {
const rawChunks = Chunks.findChunks({
autoEscape: true,
searchWords: ['text)'],
textToHighlight: '(This is text)'
})
expect(rawChunks).to.eql([
{start: 9, end: 14, highlight: false}
])
})
it('should match terms without accents against text with accents', () => {
const rawChunks = Chunks.findChunks({
sanitize: latinize,
searchWords: ['example'],
textToHighlight: 'ỆᶍǍᶆṔƚÉ'
})
expect(rawChunks).to.eql([
{start: 0, end: 7, highlight: false}
])
})
it('should support case sensitive matches', () => {
let rawChunks = Chunks.findChunks({
caseSensitive: true,
searchWords: ['t'],
textToHighlight: TEXT
})
expect(rawChunks).to.eql([
{start: 11, end: 12, highlight: false},
{start: 19, end: 20, highlight: false},
{start: 28, end: 29, highlight: false}
])
rawChunks = Chunks.findChunks({
caseSensitive: true,
searchWords: ['T'],
textToHighlight: TEXT
})
expect(rawChunks).to.eql([
{start: 0, end: 1, highlight: false}
])
})
it('should handle zero-length matches correctly', () => {
let rawChunks = Chunks.findChunks({
caseSensitive: true,
searchWords: ['.*'],
textToHighlight: TEXT
})
expect(rawChunks).to.eql([
{start: 0, end: 38, highlight: false}
])
rawChunks = Chunks.findChunks({
caseSensitive: true,
searchWords: ['w?'],
textToHighlight: TEXT
})
expect(rawChunks).to.eql([
{start: 17, end: 18, highlight: false},
{start: 22, end: 23, highlight: false}
])
})
it('should use custom findChunks', () => {
let filledInChunks = Chunks.findAll({
findChunks: () => (
[{start: 1, end: 3}]
),
searchWords: ['xxx'],
textToHighlight: TEXT
})
expect(filledInChunks).to.eql([
{start: 0, end: 1, highlight: false},
{start: 1, end: 3, highlight: true},
{start: 3, end: 38, highlight: false}
])
filledInChunks = Chunks.findAll({
findChunks: () => (
[]
),
searchWords: ['This'],
textToHighlight: TEXT
})
expect(filledInChunks).to.eql([
{start: 0, end: 38, highlight: false}
])
})
})