diff options
| author | HJ <30-hj@users.noreply.git.pleroma.social> | 2024-09-21 08:18:23 +0000 |
|---|---|---|
| committer | HJ <30-hj@users.noreply.git.pleroma.social> | 2024-09-21 08:18:23 +0000 |
| commit | f127ae307b3a444f13c6f8b75ba99cf61244677e (patch) | |
| tree | 9eff1b20ae2ab270c301e5555e9d4103c888f18b /src/services/theme_data/iss_deserializer.js | |
| parent | 23f8c08809d2ad38780584ef4f46643772cf5efe (diff) | |
| parent | 14328917f1a25770f90dc2181fad090ace4954da (diff) | |
Merge branch 'piss-serialization' into 'develop'
Pleroma ISS (interface stylesheets) implementation
See merge request pleroma/pleroma-fe!1943
Diffstat (limited to 'src/services/theme_data/iss_deserializer.js')
| -rw-r--r-- | src/services/theme_data/iss_deserializer.js | 153 |
1 files changed, 153 insertions, 0 deletions
diff --git a/src/services/theme_data/iss_deserializer.js b/src/services/theme_data/iss_deserializer.js new file mode 100644 index 00000000..5d71f35f --- /dev/null +++ b/src/services/theme_data/iss_deserializer.js @@ -0,0 +1,153 @@ +import { flattenDeep } from 'lodash' + +const parseShadow = string => { + const modes = ['_full', 'inset', 'x', 'y', 'blur', 'spread', 'color', 'alpha'] + const regexPrep = [ + // inset keyword (optional) + '^(?:(inset)\\s+)?', + // x + '(?:(-?[0-9]+(?:\\.[0-9]+)?)\\s+)', + // y + '(?:(-?[0-9]+(?:\\.[0-9]+)?)\\s+)', + // blur (optional) + '(?:(-?[0-9]+(?:\\.[0-9]+)?)\\s+)?', + // spread (optional) + '(?:(-?[0-9]+(?:\\.[0-9]+)?)\\s+)?', + // either hex, variable or function + '(#[0-9a-f]{6}|--[a-z\\-_]+|\\$[a-z\\-()_]+)', + // opacity (optional) + '(?:\\s+\\/\\s+([0-9]+(?:\\.[0-9]+)?)\\s*)?$' + ].join('') + const regex = new RegExp(regexPrep, 'gis') // global, (stable) indices, single-string + const result = regex.exec(string) + if (result == null) { + return string + } else { + const numeric = new Set(['x', 'y', 'blur', 'spread', 'alpha']) + const { x, y, blur, spread, alpha, inset, color } = Object.fromEntries(modes.map((mode, i) => { + if (numeric.has(mode)) { + return [mode, Number(result[i])] + } else if (mode === 'inset') { + return [mode, !!result[i]] + } else { + return [mode, result[i]] + } + }).filter(([k, v]) => v !== false).slice(1)) + + return { x, y, blur, spread, color, alpha, inset } + } +} +// this works nearly the same as HTML tree converter +const parseIss = (input) => { + const buffer = [{ selector: null, content: [] }] + let textBuffer = '' + + const getCurrentBuffer = () => { + let current = buffer[buffer.length - 1] + if (current == null) { + current = { selector: null, content: [] } + } + return current + } + + // Processes current line buffer, adds it to output buffer and clears line buffer + const flushText = (kind) => { + if (textBuffer === '') return + if (kind === 'content') { + getCurrentBuffer().content.push(textBuffer.trim()) + } else { + getCurrentBuffer().selector = textBuffer.trim() + } + textBuffer = '' + } + + for (let i = 0; i < input.length; i++) { + const char = input[i] + + if (char === ';') { + flushText('content') + } else if (char === '{') { + flushText('header') + } else if (char === '}') { + flushText('content') + buffer.push({ selector: null, content: [] }) + textBuffer = '' + } else { + textBuffer += char + } + } + + return buffer +} +export const deserialize = (input) => { + const ast = parseIss(input) + const finalResult = ast.filter(i => i.selector != null).map(item => { + const { selector, content } = item + let stateCount = 0 + const selectors = selector.split(/,/g) + const result = selectors.map(selector => { + const output = { component: '' } + let currentDepth = null + + selector.split(/ /g).reverse().forEach((fragment, index, arr) => { + const fragmentObject = { component: '' } + + let mode = 'component' + for (let i = 0; i < fragment.length; i++) { + const char = fragment[i] + switch (char) { + case '.': { + mode = 'variant' + fragmentObject.variant = '' + break + } + case ':': { + mode = 'state' + fragmentObject.state = fragmentObject.state || [] + stateCount++ + break + } + default: { + if (mode === 'state') { + const currentState = fragmentObject.state[stateCount - 1] + if (currentState == null) { + fragmentObject.state.push('') + } + fragmentObject.state[stateCount - 1] += char + } else { + fragmentObject[mode] += char + } + } + } + } + if (currentDepth !== null) { + currentDepth.parent = { ...fragmentObject } + currentDepth = currentDepth.parent + } else { + Object.keys(fragmentObject).forEach(key => { + output[key] = fragmentObject[key] + }) + if (index !== (arr.length - 1)) { + output.parent = { component: '' } + } + currentDepth = output + } + }) + + output.directives = Object.fromEntries(content.map(d => { + const [property, value] = d.split(':') + let realValue = value.trim() + if (property === 'shadow') { + realValue = value.split(',').map(v => parseShadow(v.trim())) + } if (!Number.isNaN(Number(value))) { + realValue = Number(value) + } + return [property, realValue] + })) + + return output + }) + return result + }) + return flattenDeep(finalResult) +} |
