-
Notifications
You must be signed in to change notification settings - Fork 454
Feat/schema css object: The style class is presented as an object in the page schema. #1749
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: develop
Are you sure you want to change the base?
Changes from all commits
a725ac3
55e464f
967a971
c69689a
372ee1a
cb95ffb
aca95bb
5081684
88cd452
96f221a
4ee3e02
b111451
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -115,22 +115,31 @@ export interface MaterialsOptions { | |||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| const generateCssString = (pageOptions: PageOptions, materialsOptions: MaterialsOptions) => { | ||||||||||||||||||||||||||||||||||
| if (!pageOptions?.pageBaseStyle?.className || !pageOptions?.pageBaseStyle?.style) { | ||||||||||||||||||||||||||||||||||
| return '' | ||||||||||||||||||||||||||||||||||
| let cssObject: any = {} | ||||||||||||||||||||||||||||||||||
| const parseStyle = (styleString: string) => { | ||||||||||||||||||||||||||||||||||
| const styleObj: any = {} | ||||||||||||||||||||||||||||||||||
| const styleItems = styleString.split(';') | ||||||||||||||||||||||||||||||||||
| styleItems.forEach((item: string) => { | ||||||||||||||||||||||||||||||||||
| if (item) { | ||||||||||||||||||||||||||||||||||
| const stylekeyValue = item.split(':') | ||||||||||||||||||||||||||||||||||
| styleObj[stylekeyValue[0]] = stylekeyValue[1].trim() | ||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||
| }) | ||||||||||||||||||||||||||||||||||
| return styleObj | ||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||
| if (pageOptions?.pageBaseStyle?.className && pageOptions?.pageBaseStyle?.style) { | ||||||||||||||||||||||||||||||||||
| cssObject[`.${pageOptions.pageBaseStyle.className}`] = parseStyle(pageOptions.pageBaseStyle.style) | ||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| const formatCssRule = (className: string, style: string) => `.${className} {\n ${style.trim()}\n}\n` | ||||||||||||||||||||||||||||||||||
| const baseStyle = `.${pageOptions.pageBaseStyle.className}{\r\n ${pageOptions.pageBaseStyle.style}\r\n}\r\n` | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| if (!materialsOptions.useBaseStyle) { | ||||||||||||||||||||||||||||||||||
| return baseStyle | ||||||||||||||||||||||||||||||||||
| if (materialsOptions.useBaseStyle) { | ||||||||||||||||||||||||||||||||||
| cssObject = { | ||||||||||||||||||||||||||||||||||
| ...cssObject, | ||||||||||||||||||||||||||||||||||
| [`.${materialsOptions.blockBaseStyle.className}`]: parseStyle(materialsOptions.blockBaseStyle.style), | ||||||||||||||||||||||||||||||||||
| [`.${materialsOptions.componentBaseStyle.className}`]: parseStyle(materialsOptions.componentBaseStyle.style) | ||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||
|
Comment on lines
+134
to
140
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Missing null guards for When Proposed fix if (materialsOptions.useBaseStyle) {
- cssObject = {
- ...cssObject,
- [`.${materialsOptions.blockBaseStyle.className}`]: parseStyle(materialsOptions.blockBaseStyle.style),
- [`.${materialsOptions.componentBaseStyle.className}`]: parseStyle(materialsOptions.componentBaseStyle.style)
- }
+ const { blockBaseStyle, componentBaseStyle } = materialsOptions
+ if (blockBaseStyle?.className && blockBaseStyle?.style) {
+ cssObject[`.${blockBaseStyle.className}`] = parseStyle(blockBaseStyle.style)
+ }
+ if (componentBaseStyle?.className && componentBaseStyle?.style) {
+ cssObject[`.${componentBaseStyle.className}`] = parseStyle(componentBaseStyle.style)
+ }
}📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| return [ | ||||||||||||||||||||||||||||||||||
| formatCssRule(pageOptions.pageBaseStyle.className, pageOptions.pageBaseStyle.style), | ||||||||||||||||||||||||||||||||||
| formatCssRule(materialsOptions.blockBaseStyle.className, materialsOptions.blockBaseStyle.style), | ||||||||||||||||||||||||||||||||||
| formatCssRule(materialsOptions.componentBaseStyle.className, materialsOptions.componentBaseStyle.style) | ||||||||||||||||||||||||||||||||||
| ].join('\n') | ||||||||||||||||||||||||||||||||||
| return cssObject | ||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| const getDefaultPage = () => { | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -108,7 +108,6 @@ import { Select as TinySelect } from '@opentiny/vue' | |
| import { useProperties, useCanvas, useHistory, useHelp } from '@opentiny/tiny-engine-meta-register' | ||
| import { LinkButton } from '@opentiny/tiny-engine-common' | ||
| import { CodeConfigurator } from '@opentiny/tiny-engine-configurator' | ||
| import { formatString } from '@opentiny/tiny-engine-common/js/ast' | ||
| import useStyle, { updateGlobalStyleStr } from '../../js/useStyle' | ||
| import { stringify, getSelectorArr } from '../../js/parser' | ||
|
|
||
|
|
@@ -448,15 +447,40 @@ watchEffect(() => { | |
| selectorValidator(classNameState.newSelector) | ||
| }) | ||
|
|
||
| const cssStringToObject = (cssString) => { | ||
| cssString = cssString.replace(/\s+/g, ' ').trim() | ||
| const result = {} | ||
| const regex = /([^{]+)\{([^}]+)\}/g | ||
|
|
||
| let match | ||
| while ((match = regex.exec(cssString)) !== null) { | ||
| const selector = match[1].trim() | ||
| const properties = match[2].trim() | ||
|
|
||
| const propertiesObj = {} | ||
|
|
||
| const propertyRegex = /([^{;]+):([^;]+)/g | ||
| let propertyMatch | ||
|
|
||
| while ((propertyMatch = propertyRegex.exec(properties)) !== null) { | ||
| const key = propertyMatch[1].trim() | ||
| const value = propertyMatch[2].trim() | ||
| propertiesObj[key] = value | ||
| } | ||
|
|
||
| result[selector] = propertiesObj | ||
| } | ||
| return result | ||
| } | ||
|
Comment on lines
+450
to
+474
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🏁 Script executed: # Locate the file and explore its structure
find . -name "index.vue" -path "*/classNamesContainer/*" -type fRepository: opentiny/tiny-engine Length of output: 135 🏁 Script executed: # Find the parser module
find . -name "parser.ts" -o -name "parser.js" | grep -E "js/parser|parser\.ts|parser\.js"Repository: opentiny/tiny-engine Length of output: 160 🏁 Script executed: # Explore the directory structure
ls -la packages/settings/styles/src/Repository: opentiny/tiny-engine Length of output: 407 🏁 Script executed: # Read the index.vue file to see the cssStringToObject function in context
wc -l packages/settings/styles/src/components/classNamesContainer/index.vueRepository: opentiny/tiny-engine Length of output: 137 🏁 Script executed: # Read the parser module
cat -n packages/settings/styles/src/js/parser.tsRepository: opentiny/tiny-engine Length of output: 7696 🏁 Script executed: # Read the relevant section of index.vue around lines 450-474
sed -n '440,490p' packages/settings/styles/src/components/classNamesContainer/index.vueRepository: opentiny/tiny-engine Length of output: 1281 🏁 Script executed: # Check the imports at the top of index.vue
head -50 packages/settings/styles/src/components/classNamesContainer/index.vueRepository: opentiny/tiny-engine Length of output: 2272 🏁 Script executed: # Search for how css is used after being saved via updateSchema
rg -n "updateSchema.*css|css.*parseList|css.*stringify" packages/settings/styles/src/components/classNamesContainer/ -A 3 -B 3Repository: opentiny/tiny-engine Length of output: 765 🏁 Script executed: # Check if there's any usage of parser elsewhere in the codebase for CSS serialization
rg -n "useStyle|parser.*css|stringify" packages/settings/styles/src/components/classNamesContainer/index.vueRepository: opentiny/tiny-engine Length of output: 310 🏁 Script executed: # Look for how css data is processed when loading/editing
rg -n "state\.cssContent|cssContent" packages/settings/styles/src/components/classNamesContainer/index.vue -B 2 -A 2Repository: opentiny/tiny-engine Length of output: 222 🏁 Script executed: # Check the useStyle hook to understand the data structure and how css is used
cat -n packages/settings/styles/src/js/useStyle.tsRepository: opentiny/tiny-engine Length of output: 13340 🏁 Script executed: # Look for updateSchema implementation to see what it expects for css parameter
rg -n "updateSchema" packages/settings/styles/src/ -B 5 -A 10 | head -60Repository: opentiny/tiny-engine Length of output: 4823 🏁 Script executed: # Look at the code-configurator component to understand what "content" parameter contains
rg -n "code-configurator" packages/settings/styles/src/components/classNamesContainer/index.vue -B 5 -A 10Repository: opentiny/tiny-engine Length of output: 1153 🏁 Script executed: # Check how save function is called and what `@save` event provides
rg -n "@save|save.*content" packages/settings/styles/src/components/classNamesContainer/index.vue -B 2 -A 2Repository: opentiny/tiny-engine Length of output: 361 🏁 Script executed: # Check what obJectCssToString does and how it relates to the reverse operation
rg -n "obJectCssToString" packages/ -B 2 -A 5Repository: opentiny/tiny-engine Length of output: 4370 🏁 Script executed: # Get the full implementation of obJectCssToString
sed -n '462,500p' packages/utils/src/utils/index.tsRepository: opentiny/tiny-engine Length of output: 482 🏁 Script executed: # Test cssStringToObject with complex CSS to understand its limitations
python3 << 'EOF'
import re
def cssStringToObject(cssString):
cssString = cssString.replace(/\s+/g, ' ').strip()
result = {}
regex = r'([^{]+)\{([^}]+)\}'
for match in re.finditer(regex, cssString):
selector = match.group(1).strip()
properties = match.group(2).strip()
propertiesObj = {}
propertyRegex = r'([^{;]+):([^;]+)'
for propMatch in re.finditer(propertyRegex, properties):
key = propMatch.group(1).strip()
value = propMatch.group(2).strip()
propertiesObj[key] = value
result[selector] = propertiesObj
return result
# Test cases
test_cases = [
('.simple { color: red; }', 'Simple selector'),
('.a, .b { color: blue; }', 'Multiple selectors (comma)'),
('@media (max-width: 600px) { .box { width: 100%; } }', '@media query'),
('@keyframes slide { from { left: 0; } to { left: 100%; } }', '@keyframes'),
('.with-pseudo:hover { color: green; }', 'Pseudo-class'),
('.content { content: ";"; }', 'Content with semicolon'),
('.test { font-family: "Arial, sans-serif"; }', 'Value with comma'),
]
for css, description in test_cases:
result = cssStringToObject(css)
print(f"\n{description}:")
print(f" Input: {css}")
print(f" Output: {result}")
print(f" Success: {bool(result)}")
EOFRepository: opentiny/tiny-engine Length of output: 150 🏁 Script executed: # Test the regex parser with specific edge cases using JavaScript/Node
node << 'EOF'
const cssStringToObject = (cssString) => {
cssString = cssString.replace(/\s+/g, ' ').trim()
const result = {}
const regex = /([^{]+)\{([^}]+)\}/g
let match
while ((match = regex.exec(cssString)) !== null) {
const selector = match[1].trim()
const properties = match[2].trim()
const propertiesObj = {}
const propertyRegex = /([^{;]+):([^;]+)/g
let propertyMatch
while ((propertyMatch = propertyRegex.exec(properties)) !== null) {
const key = propertyMatch[1].trim()
const value = propertyMatch[2].trim()
propertiesObj[key] = value
}
result[selector] = propertiesObj
}
return result
}
const testCases = [
{ input: '.simple { color: red; }', desc: 'Simple selector' },
{ input: '.a, .b { color: blue; }', desc: 'Multiple selectors' },
{ input: '@media (max-width: 600px) { .box { width: 100%; } }', desc: '@media query' },
{ input: '.content { content: ";"; }', desc: 'Content with semicolon' },
{ input: '.test { background: url("data:image/svg+xml;..."); }', desc: 'Value with braces' },
]
testCases.forEach(({ input, desc }) => {
const result = cssStringToObject(input)
console.log(`\n${desc}:`)
console.log(` Input: ${input}`)
console.log(` Output: ${JSON.stringify(result)}`)
console.log(` Data loss: ${Object.keys(result).length === 0 ? 'YES' : 'PARTIAL'}`)
})
EOFRepository: opentiny/tiny-engine Length of output: 728 🏁 Script executed: # Check what the parser module returns for complex CSS
sed -n '75,144p' packages/settings/styles/src/js/parser.ts | head -60Repository: opentiny/tiny-engine Length of output: 1276 🏁 Script executed: # Verify the exact format updateSchema expects by checking if there are type definitions or usage patterns
rg -n "updateSchema.*css|css.*:" packages/settings/styles/src/ -A 2 -B 2 | head -40Repository: opentiny/tiny-engine Length of output: 2311 🏁 Script executed: # Check if there's documentation or schema definition for what the css property should be
find packages -name "*.ts" -o -name "*.d.ts" | xargs grep -l "schema.*css\|css.*schema" | head -5Repository: opentiny/tiny-engine Length of output: 107 🏁 Script executed: # Let's trace through what happens when updateSchema receives css parameter
# Check the useEditor to see the pattern more clearly
cat -n packages/settings/styles/src/js/useEditor.tsRepository: opentiny/tiny-engine Length of output: 2686 🏁 Script executed: # Verify what format the save function should pass
# Look at the complete flow in index.vue around the save function
sed -n '470,490p' packages/settings/styles/src/components/classNamesContainer/index.vueRepository: opentiny/tiny-engine Length of output: 524 🏁 Script executed: # Check what type of css parameter updateSchema actually expects
rg -n "pageSchema\.css\s*=" packages/settings/styles/src/ -B 2 -A 2Repository: opentiny/tiny-engine Length of output: 46 🏁 Script executed: # Look for where the schema.css is read back
rg -n "getPageSchema.*css|schema\.css" packages/settings/styles/src/js/useStyle.ts -B 2 -A 5Repository: opentiny/tiny-engine Length of output: 336 🏁 Script executed: # Check the watch that loads CSS from schema to understand the expected format
sed -n '176,189p' packages/settings/styles/src/js/useStyle.tsRepository: opentiny/tiny-engine Length of output: 424 🏁 Script executed: # Understand the data flow: what gets stored and retrieved
# Check if css can be string or object in the schema
rg -n "css\s*:" packages/ -t ts -t vue | grep -E "schema.*css|css.*schema" | head -20Repository: opentiny/tiny-engine Length of output: 91 🏁 Script executed: # Trace through a complete example: save -> updateSchema -> load
# Check how updateGlobalStyleStr preserves data
sed -n '268,293p' packages/settings/styles/src/js/useStyle.tsRepository: opentiny/tiny-engine Length of output: 692 🏁 Script executed: # Final verification: trace the exact data type expected by updateSchema
# Look at how useEditor passes css to updateSchema (line 61)
sed -n '59,64p' packages/settings/styles/src/js/useEditor.tsRepository: opentiny/tiny-engine Length of output: 216 🏁 Script executed: # And compare to how updateGlobalStyleStr does it
sed -n '261,266p' packages/settings/styles/src/js/useStyle.tsRepository: opentiny/tiny-engine Length of output: 225 🏁 Script executed: # Verify what format the schema expects when it's loaded back
# Check line 180 in useStyle.ts more carefully
node << 'EOF'
// Simulate the obJectCssToString with different inputs
function obJectCssToString(css) {
if (typeof css === 'string') {
return css
}
let cssString = ''
for (const selector in css) {
const properties = css[selector]
let ruleString = `${selector} {\r\n`
for (const property in properties) {
ruleString += ` ${property}: ${properties[property]};\r\n`
}
ruleString += '}\n'
cssString += ruleString
}
return cssString
}
// Test with regex-based parser output
const regexOutput = {
".simple": { "color": "red" },
".a, .b": { "color": "blue" } // Multiple selectors stored as-is
}
// Test with proper parser output structure
const parserOutput = {
".simple": { "mouseState": "", "pureSelector": ".simple", "rules": { "color": "red" } },
".a": { "mouseState": "", "pureSelector": ".a", "rules": { "color": "blue" } }
}
console.log("Regex output converted:")
console.log(obJectCssToString(regexOutput))
console.log("\nParser output with rules only:")
console.log(obJectCssToString(Object.fromEntries(
Object.entries(parserOutput).map(([sel, val]) => [sel, val.rules])
)))
EOFRepository: opentiny/tiny-engine Length of output: 225 🏁 Script executed: # Final check: what does updateSchema actually store and how is it retrieved?
# Look for the actual updateSchema implementation
rg -n "const updateSchema|export.*updateSchema" packages/ -t ts -A 5 | head -30Repository: opentiny/tiny-engine Length of output: 1077 🏁 Script executed: # Verify the actual type that updateSchema stores for css
rg -n "css:" packages/canvas -t ts | grep -v "cssContent\|cssString\|cssClass" | head -20Repository: opentiny/tiny-engine Length of output: 401 Regex CSS parser causes silent data loss for complex CSS rules.
Use the existing parser and stringify utilities consistently, following the pattern in -const cssStringToObject = (cssString) => {
- cssString = cssString.replace(/\s+/g, ' ').trim()
- const result = {}
- const regex = /([^{]+)\{([^}]+)\}/g
- ...
- return result
-}
const save = ({ content }) => {
- const cssString = cssStringToObject(content)
+ const { parseList, styleObject } = parser(content)
+ const cssString = stringify(parseList, styleObject)
const { addHistory } = useHistory()
const { updateRect } = useCanvas().canvasApi.value
const { updateSchema } = useCanvas()This preserves all CSS structures through the round-trip. 🤖 Prompt for AI Agents |
||
|
|
||
| const save = ({ content }) => { | ||
| const cssString = formatString(content.replace(/"/g, "'"), 'css') | ||
| const cssString = cssStringToObject(content) | ||
| const { addHistory } = useHistory() | ||
| const { updateRect } = useCanvas().canvasApi.value | ||
| const { updateSchema } = useCanvas() | ||
|
|
||
| updateSchema({ css: cssString }) | ||
| state.schemaUpdateKey++ | ||
|
|
||
| addHistory() | ||
| updateRect() | ||
| } | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -452,3 +452,29 @@ export const obj2StyleString = (obj: any) => { | |
| .join('; ') | ||
| ) | ||
| } | ||
|
|
||
| /** | ||
| * JSON转样式字符串 | ||
| * @param {*} string | ||
| * @returns | ||
| */ | ||
|
|
||
| export const obJectCssToString = (css) => { | ||
| if (typeof css === 'string') { | ||
| return css | ||
| } | ||
| let cssString = '' | ||
|
|
||
| for (const selector in css) { | ||
| const properties = css[selector] | ||
| let ruleString = `${selector} {\r\n` | ||
|
|
||
| for (const property in properties) { | ||
| ruleString += ` ${property}: ${properties[property]};\r\n` | ||
| } | ||
|
|
||
| ruleString += '}\n' | ||
| cssString += ruleString | ||
| } | ||
| return cssString | ||
|
Comment on lines
+462
to
+479
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Guard against 🐛 Suggested fix export const obJectCssToString = (css) => {
+ if (!css) {
+ return ''
+ }
if (typeof css === 'string') {
return css
}
+ if (typeof css !== 'object') {
+ return ''
+ }
let cssString = ''
- for (const selector in css) {
- const properties = css[selector]
+ for (const [selector, properties] of Object.entries(css)) {
let ruleString = `${selector} {\r\n`
- for (const property in properties) {
- ruleString += ` ${property}: ${properties[property]};\r\n`
+ for (const [property, value] of Object.entries(properties || {})) {
+ ruleString += ` ${property}: ${value};\r\n`
}🤖 Prompt for AI Agents |
||
| } | ||
Uh oh!
There was an error while loading. Please reload this page.