Merge branch 'fix-style-editors' into 'develop'

Fix style editors, properly default from unavailable adopted sheets

See merge request pleroma/pleroma-fe!2194
This commit is contained in:
HJ 2025-07-03 08:25:18 +00:00
commit c179daaf80
17 changed files with 347 additions and 331 deletions

View file

View file

@ -11,9 +11,7 @@
<link rel="preload" href="/static/pleromatan_apology_fox_small.webp" as="image" /> <link rel="preload" href="/static/pleromatan_apology_fox_small.webp" as="image" />
<!-- putting styles here to avoid having to wait for styles to load up --> <!-- putting styles here to avoid having to wait for styles to load up -->
<link rel="stylesheet" id="splashscreen" href="/static/splash.css" /> <link rel="stylesheet" id="splashscreen" href="/static/splash.css" />
<link rel="stylesheet" id="pleroma-eager-styles" type="text/css" href="/static/empty.css" /> <link rel="stylesheet" id="custom-styles-holder" type="text/css" href="/static/empty.css" />
<link rel="stylesheet" id="pleroma-lazy-styles" type="text/css" href="/static/empty.css" />
<link rel="stylesheet" id="theme-holder" type="text/css" href="/static/empty.css" />
<!--server-generated-meta--> <!--server-generated-meta-->
</head> </head>
<body> <body>

View file

@ -23,7 +23,7 @@
"@fortawesome/free-regular-svg-icons": "6.7.2", "@fortawesome/free-regular-svg-icons": "6.7.2",
"@fortawesome/free-solid-svg-icons": "6.7.2", "@fortawesome/free-solid-svg-icons": "6.7.2",
"@fortawesome/vue-fontawesome": "3.0.8", "@fortawesome/vue-fontawesome": "3.0.8",
"@floatingghost/pinch-zoom-element": "1.3.1", "@kazvmoe-infra/pinch-zoom-element": "1.3.0",
"@kazvmoe-infra/unicode-emoji-json": "0.4.0", "@kazvmoe-infra/unicode-emoji-json": "0.4.0",
"@ruffle-rs/ruffle": "0.1.0-nightly.2025.1.13", "@ruffle-rs/ruffle": "0.1.0-nightly.2025.1.13",
"@vuelidate/core": "2.0.3", "@vuelidate/core": "2.0.3",

View file

@ -1 +0,0 @@
// nothing here //

View file

@ -3,7 +3,7 @@
@use "panel"; @use "panel";
@import '@fortawesome/fontawesome-svg-core/styles.css'; @import '@fortawesome/fontawesome-svg-core/styles.css';
@import '@floatingghost/pinch-zoom-element/dist/pinch-zoom.css'; @import '@kazvmoe-infra/pinch-zoom-element/dist/pinch-zoom.css';
:root { :root {
--status-margin: 0.75em; --status-margin: 0.75em;

View file

@ -0,0 +1,82 @@
import Checkbox from 'src/components/checkbox/checkbox.vue'
import ColorInput from 'src/components/color_input/color_input.vue'
import genRandomSeed from 'src/services/random_seed/random_seed.service.js'
import { createStyleSheet, adoptStyleSheets } from 'src/services/style_setter/style_setter.js'
export default {
components: {
Checkbox,
ColorInput
},
props: [
'shadow',
'shadowControl',
'previewClass',
'previewStyle',
'previewCss',
'disabled',
'invalid',
'noColorControl'
],
emits: ['update:shadow'],
data () {
return {
colorOverride: undefined,
lightGrid: false,
zoom: 100,
randomSeed: genRandomSeed()
}
},
mounted () {
this.update()
},
computed: {
hideControls () {
return typeof this.shadow === 'string'
}
},
watch: {
previewCss () {
this.update()
},
previewStyle () {
this.update()
},
zoom () {
this.update()
}
},
methods: {
updateProperty (axis, value) {
this.$emit('update:shadow', { axis, value: Number(value) })
},
update () {
const sheet = createStyleSheet('style-component-preview')
sheet.clear()
const result = [this.previewCss]
if (this.colorOverride) result.push(`--background: ${this.colorOverride}`)
const styleRule = [
'#component-preview-', this.randomSeed, ' {\n',
'.preview-block {\n',
`zoom: ${this.zoom / 100};`,
this.previewStyle,
'\n}',
'\n}'
].join('')
sheet.addRule(styleRule)
sheet.addRule([
'#component-preview-', this.randomSeed, ' {\n',
...result,
'\n}'
].join(''))
sheet.ready = true
adoptStyleSheets()
}
}
}

View file

@ -0,0 +1,151 @@
.ComponentPreview {
display: grid;
grid-template-columns: 1em 1fr 1fr 1em;
grid-template-rows: 2em 1fr 1fr 1fr 1em 2em max-content;
grid-template-areas:
"header header header header "
"preview preview preview y-slide"
"preview preview preview y-slide"
"preview preview preview y-slide"
"x-slide x-slide x-slide . "
"x-num x-num y-num y-num "
"assists assists assists assists";
grid-gap: 0.5em;
&:not(.-shadow-controls) {
grid-template-areas:
"header header header header "
"preview preview preview y-slide"
"preview preview preview y-slide"
"preview preview preview y-slide"
"assists assists assists assists";
grid-template-rows: 2em 1fr 1fr 1fr max-content;
}
.header {
grid-area: header;
place-self: baseline center;
line-height: 2;
}
.invalid-container {
position: absolute;
inset: 0;
display: grid;
place-items: center center;
background-color: rgb(100 0 0 / 50%);
.alert {
padding: 0.5em 1em;
}
}
.assists {
grid-area: assists;
display: grid;
grid-auto-flow: row;
grid-auto-rows: 2em;
grid-gap: 0.5em;
}
.input-light-grid {
justify-self: center;
}
.input-number {
min-width: 2em;
}
.x-shift-number {
grid-area: x-num;
justify-self: right;
}
.y-shift-number {
grid-area: y-num;
justify-self: left;
}
.x-shift-number,
.y-shift-number {
input {
max-width: 4em;
}
}
.x-shift-slider {
grid-area: x-slide;
height: auto;
align-self: start;
min-width: 10em;
}
.y-shift-slider {
grid-area: y-slide;
writing-mode: vertical-lr;
justify-self: left;
min-height: 10em;
}
.x-shift-slider,
.y-shift-slider {
padding: 0;
}
.preview-window {
--__grid-color1: rgb(102 102 102);
--__grid-color2: rgb(153 153 153);
--__grid-color1-disabled: rgb(102 102 102 / 20%);
--__grid-color2-disabled: rgb(153 153 153 / 20%);
&.-light-grid {
--__grid-color1: rgb(205 205 205);
--__grid-color2: rgb(255 255 255);
--__grid-color1-disabled: rgb(205 205 205 / 20%);
--__grid-color2-disabled: rgb(255 255 255 / 20%);
}
position: relative;
grid-area: preview;
aspect-ratio: 1;
display: flex;
align-items: center;
justify-content: center;
min-width: 10em;
min-height: 10em;
background-color: var(--__grid-color2);
background-image:
linear-gradient(45deg, var(--__grid-color1) 25%, transparent 25%),
linear-gradient(-45deg, var(--__grid-color1) 25%, transparent 25%),
linear-gradient(45deg, transparent 75%, var(--__grid-color1) 75%),
linear-gradient(-45deg, transparent 75%, var(--__grid-color1) 75%);
background-size: 20px 20px;
background-position: 0 0, 0 10px, 10px -10px, -10px 0;
border-radius: var(--roundness);
&.disabled {
background-color: var(--__grid-color2-disabled);
background-image:
linear-gradient(45deg, var(--__grid-color1-disabled) 25%, transparent 25%),
linear-gradient(-45deg, var(--__grid-color1-disabled) 25%, transparent 25%),
linear-gradient(45deg, transparent 75%, var(--__grid-color1-disabled) 75%),
linear-gradient(-45deg, transparent 75%, var(--__grid-color1-disabled) 75%);
}
.preview-block {
background: var(--background, var(--bg));
display: flex;
justify-content: center;
align-items: center;
min-width: 33%;
min-height: 33%;
max-width: 80%;
max-height: 80%;
border-width: 0;
border-style: solid;
border-color: var(--border);
border-radius: var(--roundness);
box-shadow: var(--shadow);
}
}
}

View file

@ -1,14 +1,9 @@
<template> <template>
<div <div
:id="'component-preview-' + randomSeed"
class="ComponentPreview" class="ComponentPreview"
:class="{ '-shadow-controls': shadowControl }" :class="{ '-shadow-controls': shadowControl }"
> >
<!-- eslint-disable vue/no-v-html vue/no-v-text-v-html-on-component -->
<component
:is="'style'"
v-html="previewCss"
/>
<!-- eslint-enable vue/no-v-html vue/no-v-text-v-html-on-component -->
<label <label
v-show="shadowControl" v-show="shadowControl"
role="heading" role="heading"
@ -74,7 +69,6 @@
<div <div
class="preview-block" class="preview-block"
:class="previewClass" :class="previewClass"
:style="style"
> >
{{ $t('settings.style.themes3.editor.test_string') }} {{ $t('settings.style.themes3.editor.test_string') }}
</div> </div>
@ -116,203 +110,5 @@
</div> </div>
</template> </template>
<script> <script src="./component_preview.js" />
import Checkbox from 'src/components/checkbox/checkbox.vue' <style src="./component_preview.scss" lang="scss" />
import ColorInput from 'src/components/color_input/color_input.vue'
export default {
components: {
Checkbox,
ColorInput
},
props: [
'shadow',
'shadowControl',
'previewClass',
'previewStyle',
'previewCss',
'disabled',
'invalid',
'noColorControl'
],
emits: ['update:shadow'],
data () {
return {
colorOverride: undefined,
lightGrid: false,
zoom: 100
}
},
computed: {
style () {
const result = [
this.previewStyle,
`zoom: ${this.zoom / 100}`
]
if (this.colorOverride) result.push(`--background: ${this.colorOverride}`)
return result
},
hideControls () {
return typeof this.shadow === 'string'
}
},
methods: {
updateProperty (axis, value) {
this.$emit('update:shadow', { axis, value: Number(value) })
}
}
}
</script>
<style lang="scss">
.ComponentPreview {
display: grid;
grid-template-columns: 1em 1fr 1fr 1em;
grid-template-rows: 2em 1fr 1fr 1fr 1em 2em max-content;
grid-template-areas:
"header header header header "
"preview preview preview y-slide"
"preview preview preview y-slide"
"preview preview preview y-slide"
"x-slide x-slide x-slide . "
"x-num x-num y-num y-num "
"assists assists assists assists";
grid-gap: 0.5em;
&:not(.-shadow-controls) {
grid-template-areas:
"header header header header "
"preview preview preview y-slide"
"preview preview preview y-slide"
"preview preview preview y-slide"
"assists assists assists assists";
grid-template-rows: 2em 1fr 1fr 1fr max-content;
}
.header {
grid-area: header;
place-self: baseline center;
line-height: 2;
}
.invalid-container {
position: absolute;
inset: 0;
display: grid;
place-items: center center;
background-color: rgb(100 0 0 / 50%);
.alert {
padding: 0.5em 1em;
}
}
.assists {
grid-area: assists;
display: grid;
grid-auto-flow: row;
grid-auto-rows: 2em;
grid-gap: 0.5em;
}
.input-light-grid {
justify-self: center;
}
.input-number {
min-width: 2em;
}
.x-shift-number {
grid-area: x-num;
justify-self: right;
}
.y-shift-number {
grid-area: y-num;
justify-self: left;
}
.x-shift-number,
.y-shift-number {
input {
max-width: 4em;
}
}
.x-shift-slider {
grid-area: x-slide;
height: auto;
align-self: start;
min-width: 10em;
}
.y-shift-slider {
grid-area: y-slide;
writing-mode: vertical-lr;
justify-self: left;
min-height: 10em;
}
.x-shift-slider,
.y-shift-slider {
padding: 0;
}
.preview-window {
--__grid-color1: rgb(102 102 102);
--__grid-color2: rgb(153 153 153);
--__grid-color1-disabled: rgb(102 102 102 / 20%);
--__grid-color2-disabled: rgb(153 153 153 / 20%);
&.-light-grid {
--__grid-color1: rgb(205 205 205);
--__grid-color2: rgb(255 255 255);
--__grid-color1-disabled: rgb(205 205 205 / 20%);
--__grid-color2-disabled: rgb(255 255 255 / 20%);
}
position: relative;
grid-area: preview;
aspect-ratio: 1;
display: flex;
align-items: center;
justify-content: center;
min-width: 10em;
min-height: 10em;
background-color: var(--__grid-color2);
background-image:
linear-gradient(45deg, var(--__grid-color1) 25%, transparent 25%),
linear-gradient(-45deg, var(--__grid-color1) 25%, transparent 25%),
linear-gradient(45deg, transparent 75%, var(--__grid-color1) 75%),
linear-gradient(-45deg, transparent 75%, var(--__grid-color1) 75%);
background-size: 20px 20px;
background-position: 0 0, 0 10px, 10px -10px, -10px 0;
border-radius: var(--roundness);
&.disabled {
background-color: var(--__grid-color2-disabled);
background-image:
linear-gradient(45deg, var(--__grid-color1-disabled) 25%, transparent 25%),
linear-gradient(-45deg, var(--__grid-color1-disabled) 25%, transparent 25%),
linear-gradient(45deg, transparent 75%, var(--__grid-color1-disabled) 75%),
linear-gradient(-45deg, transparent 75%, var(--__grid-color1-disabled) 75%);
}
.preview-block {
background: var(--background, var(--bg));
display: flex;
justify-content: center;
align-items: center;
min-width: 33%;
min-height: 33%;
max-width: 80%;
max-height: 80%;
border-width: 0;
border-style: solid;
border-color: var(--border);
border-radius: var(--roundness);
box-shadow: var(--shadow);
}
}
}
</style>

View file

@ -1,4 +1,4 @@
import PinchZoom from '@floatingghost/pinch-zoom-element' import PinchZoom from '@kazvmoe-infra/pinch-zoom-element'
export default { export default {
methods: { methods: {

View file

@ -15,6 +15,7 @@ import {
getCssRules getCssRules
} from 'src/services/theme_data/css_utils.js' } from 'src/services/theme_data/css_utils.js'
import { deserialize } from 'src/services/theme_data/iss_deserializer.js' import { deserialize } from 'src/services/theme_data/iss_deserializer.js'
import { createStyleSheet, adoptStyleSheets } from 'src/services/style_setter/style_setter.js'
import SharedComputedObject from '../helpers/shared_computed_object.js' import SharedComputedObject from '../helpers/shared_computed_object.js'
import ProfileSettingIndicator from '../helpers/profile_setting_indicator.vue' import ProfileSettingIndicator from '../helpers/profile_setting_indicator.vue'
@ -410,16 +411,17 @@ const AppearanceTab = {
this.compilationCache[key] = theme3 this.compilationCache[key] = theme3
} }
const styleEl = document.getElementById('theme-holder')
const styleSheet = styleEl.sheet const sheet = createStyleSheet('appearance-tab-previews')
styleSheet.insertRule([ sheet.addRule([
'#theme-preview-', '#theme-preview-',
key, key,
' {\n', ' {\n',
getCssRules(theme3.eager).join('\n'), getCssRules(theme3.eager).join('\n'),
'\n}' '\n}'
].join(''), 'index-max') ].join(''))
sheet.ready = true
adoptStyleSheets()
} }
} }
} }

View file

@ -1,4 +1,4 @@
import { ref, reactive, computed, watch, watchEffect, provide, getCurrentInstance } from 'vue' import { ref, reactive, computed, watch, provide, getCurrentInstance } from 'vue'
import { useInterfaceStore } from 'src/stores/interface' import { useInterfaceStore } from 'src/stores/interface'
import { get, set, unset, throttle } from 'lodash' import { get, set, unset, throttle } from 'lodash'
@ -19,11 +19,9 @@ import Preview from '../theme_tab/theme_preview.vue'
import VirtualDirectivesTab from './virtual_directives_tab.vue' import VirtualDirectivesTab from './virtual_directives_tab.vue'
import { createStyleSheet, adoptStyleSheets } from 'src/services/style_setter/style_setter.js'
import { init, findColor } from 'src/services/theme_data/theme_data_3.service.js' import { init, findColor } from 'src/services/theme_data/theme_data_3.service.js'
import { import { getCssRules } from 'src/services/theme_data/css_utils.js'
getCssRules,
getScopedVersion
} from 'src/services/theme_data/css_utils.js'
import { serialize } from 'src/services/theme_data/iss_serializer.js' import { serialize } from 'src/services/theme_data/iss_serializer.js'
import { deserializeShadow, deserialize } from 'src/services/theme_data/iss_deserializer.js' import { deserializeShadow, deserialize } from 'src/services/theme_data/iss_deserializer.js'
import { import {
@ -670,7 +668,7 @@ export default {
}) })
exports.clearStyle = () => { exports.clearStyle = () => {
onImport(interfaceStore().styleDataUsed) onImport(interfaceStore.styleDataUsed)
} }
exports.exportStyle = () => { exports.exportStyle = () => {
@ -688,19 +686,26 @@ export default {
const overallPreviewRules = ref([]) const overallPreviewRules = ref([])
exports.overallPreviewRules = overallPreviewRules exports.overallPreviewRules = overallPreviewRules
const overallPreviewCssRules = ref([]) watch([overallPreviewRules], () => {
watchEffect(throttle(() => { let css = null
try { try {
overallPreviewCssRules.value = getScopedVersion( css = getCssRules(overallPreviewRules.value).map(r => r.replace('html', '&'))
getCssRules(overallPreviewRules.value),
'#edited-style-preview'
).join('\n')
} catch (e) { } catch (e) {
console.error(e) console.error(e)
return
} }
}, 500))
exports.overallPreviewCssRules = overallPreviewCssRules const sheet = createStyleSheet('style-tab-overall-preview')
sheet.clear()
sheet.addRule([
'#edited-style-preview {\n',
css.join('\n'),
'\n}'
].join(''))
sheet.ready = true
adoptStyleSheets()
})
const updateOverallPreview = throttle(() => { const updateOverallPreview = throttle(() => {
try { try {
@ -724,12 +729,12 @@ export default {
console.error('Could not compile preview theme', e) console.error('Could not compile preview theme', e)
return null return null
} }
}, 5000) }, 1000)
// //
// Apart from "hover" we can't really show how component looks like in // Apart from "hover" we can't really show how component looks like in
// certain states, so we have to fake them. // certain states, so we have to fake them.
const simulatePseudoSelectors = (css, prefix) => css const simulatePseudoSelectors = (css, prefix) => css
.replace(prefix, '.component-preview .preview-block') .replace(prefix, '.preview-block')
.replace(':active', '.preview-active') .replace(':active', '.preview-active')
.replace(':hover', '.preview-hover') .replace(':hover', '.preview-hover')
.replace(':active', '.preview-active') .replace(':active', '.preview-active')

View file

@ -6,14 +6,6 @@
<div class="setting-item heading"> <div class="setting-item heading">
<h2> {{ $t('settings.style.themes3.editor.title') }} </h2> <h2> {{ $t('settings.style.themes3.editor.title') }} </h2>
<div class="meta-preview"> <div class="meta-preview">
<!-- eslint-disable vue/no-v-text-v-html-on-component -->
<!-- eslint-disable vue/no-v-html -->
<component
:is="'style'"
v-html="overallPreviewCssRules"
/>
<!-- eslint-enable vue/no-v-html -->
<!-- eslint-enable vue/no-v-text-v-html-on-component -->
<Preview id="edited-style-preview" /> <Preview id="edited-style-preview" />
<teleport <teleport
v-if="isActive" v-if="isActive"
@ -155,12 +147,6 @@
</ul> </ul>
</div> </div>
<div class="preview-container"> <div class="preview-container">
<!-- eslint-disable vue/no-v-html vue/no-v-text-v-html-on-component -->
<component
:is="'style'"
v-html="previewCss"
/>
<!-- eslint-enable vue/no-v-html vue/no-v-text-v-html-on-component -->
<ComponentPreview <ComponentPreview
class="component-preview" class="component-preview"
:show-text="componentHas('Text')" :show-text="componentHas('Text')"

View file

@ -31,6 +31,7 @@ import {
getCssRules, getCssRules,
getScopedVersion getScopedVersion
} from 'src/services/theme_data/css_utils.js' } from 'src/services/theme_data/css_utils.js'
import { createStyleSheet, adoptStyleSheets } from 'src/services/style_setter/style_setter.js'
import ColorInput from 'src/components/color_input/color_input.vue' import ColorInput from 'src/components/color_input/color_input.vue'
import RangeInput from 'src/components/range_input/range_input.vue' import RangeInput from 'src/components/range_input/range_input.vue'
@ -68,7 +69,6 @@ const colorConvert = (color) => {
export default { export default {
data () { data () {
return { return {
themeV3Preview: [],
themeImporter: newImporter({ themeImporter: newImporter({
validator: this.importValidator, validator: this.importValidator,
onImport: this.onImport, onImport: this.onImport,
@ -697,10 +697,16 @@ export default {
liteMode: true liteMode: true
}) })
this.themeV3Preview = getScopedVersion( const sheet = createStyleSheet('theme-tab-overall-preview')
const rule = getScopedVersion(
getCssRules(theme3.eager), getCssRules(theme3.eager),
'#theme-preview' '&'
).join('\n') ).join('\n')
sheet.clear()
sheet.addRule('#theme-preview {\n' + rule + '\n}')
sheet.ready = true
adoptStyleSheets()
} }
}, },
watch: { watch: {

View file

@ -123,12 +123,6 @@
</div> </div>
</div> </div>
<!-- eslint-disable vue/no-v-html vue/no-v-text-v-html-on-component -->
<component
:is="'style'"
v-html="themeV3Preview"
/>
<!-- eslint-enable vue/no-v-html vue/no-v-text-v-html-on-component -->
<preview id="theme-preview" /> <preview id="theme-preview" />
<div> <div>

View file

@ -1,47 +1,68 @@
import { init, getEngineChecksum } from '../theme_data/theme_data_3.service.js' import { init, getEngineChecksum } from '../theme_data/theme_data_3.service.js'
import { getCssRules } from '../theme_data/css_utils.js' import { getCssRules } from '../theme_data/css_utils.js'
import { defaultState } from 'src/modules/default_config_state.js' import { defaultState } from 'src/modules/default_config_state.js'
import { chunk } from 'lodash' import { chunk, throttle } from 'lodash'
import localforage from 'localforage' import localforage from 'localforage'
// On platforms where this is not supported, it will return undefined // On platforms where this is not supported, it will return undefined
// Otherwise it will return an array // Otherwise it will return an array
const supportsAdoptedStyleSheets = !!document.adoptedStyleSheets const supportsAdoptedStyleSheets = !!document.adoptedStyleSheets
const createStyleSheet = (id) => { const stylesheets = {}
export const createStyleSheet = (id) => {
if (stylesheets[id]) return stylesheets[id]
const newStyleSheet = {
rules: [],
ready: false,
clear () {
this.rules = []
},
addRule (rule) {
let newRule = rule
if (!CSS.supports?.('backdrop-filter', 'blur()')) {
newRule = newRule.replace(/backdrop-filter:[^;]+;/g, '') // Remove backdrop-filter
}
this.rules.push(
newRule
.replace(/var\(--shadowFilter\)[^;]*;/g, '') // Remove shadowFilter references
)
}
}
stylesheets[id] = newStyleSheet
return newStyleSheet
}
export const adoptStyleSheets = throttle(() => {
if (supportsAdoptedStyleSheets) { if (supportsAdoptedStyleSheets) {
return { document.adoptedStyleSheets = Object
el: null, .values(stylesheets)
sheet: new CSSStyleSheet(), .filter(x => x.ready)
rules: [] .map(x => {
} const css = new CSSStyleSheet()
} x.rules.forEach(r => css.insertRule(r, css.cssRules.length))
return css
})
} else {
const holder = document.getElementById('custom-styles-holder')
const el = document.getElementById(id) Object
// Clear all rules in it .values(stylesheets)
for (let i = el.sheet.cssRules.length - 1; i >= 0; --i) { .forEach(sheet => {
el.sheet.deleteRule(i) sheet.rules.forEach(r => holder.sheet.insertRule(r))
} })
return {
el,
sheet: el.sheet,
rules: []
}
}
const EAGER_STYLE_ID = 'pleroma-eager-styles'
const LAZY_STYLE_ID = 'pleroma-lazy-styles'
const adoptStyleSheets = (styles) => {
if (supportsAdoptedStyleSheets) {
document.adoptedStyleSheets = styles.map(s => s.sheet)
} }
// Some older browsers do not support document.adoptedStyleSheets. // Some older browsers do not support document.adoptedStyleSheets.
// In this case, we use the <style> elements. // In this case, we use the <style> elements.
// Since the <style> elements we need are already in the DOM, there // Since the <style> elements we need are already in the DOM, there
// is nothing to do here. // is nothing to do here.
} }, 500)
const EAGER_STYLE_ID = 'pleroma-eager-styles'
const LAZY_STYLE_ID = 'pleroma-lazy-styles'
export const generateTheme = (inputRuleset, callbacks, debug) => { export const generateTheme = (inputRuleset, callbacks, debug) => {
const { const {
@ -97,10 +118,13 @@ export const tryLoadCache = async () => {
const eagerStyles = createStyleSheet(EAGER_STYLE_ID) const eagerStyles = createStyleSheet(EAGER_STYLE_ID)
const lazyStyles = createStyleSheet(LAZY_STYLE_ID) const lazyStyles = createStyleSheet(LAZY_STYLE_ID)
cache.data[0].forEach(rule => eagerStyles.sheet.insertRule(rule, 'index-max')) cache.data[0].forEach(rule => eagerStyles.addRule(rule))
cache.data[1].forEach(rule => lazyStyles.sheet.insertRule(rule, 'index-max')) cache.data[1].forEach(rule => lazyStyles.addRule(rule))
adoptStyleSheets([eagerStyles, lazyStyles]) eagerStyles.ready = true
lazyStyles.ready = true
adoptStyleSheets()
console.info(`Loaded theme from cache`) console.info(`Loaded theme from cache`)
return true return true
@ -123,54 +147,26 @@ export const applyTheme = (
const eagerStyles = createStyleSheet(EAGER_STYLE_ID) const eagerStyles = createStyleSheet(EAGER_STYLE_ID)
const lazyStyles = createStyleSheet(LAZY_STYLE_ID) const lazyStyles = createStyleSheet(LAZY_STYLE_ID)
const insertRule = (styles, rule) => {
try {
// Try to use modern syntax first
try {
styles.sheet.insertRule(rule, 'index-max')
styles.rules.push(rule)
} catch {
// Fallback for older browsers that don't support 'index-max'
styles.sheet.insertRule(rule, styles.sheet.cssRules.length)
styles.rules.push(rule)
}
} catch (e) {
console.warn('Can\'t insert rule due to lack of support', e, rule)
// Try to sanitize the rule for better compatibility
try {
// Remove any potentially problematic CSS features
let sanitizedRule = rule
.replace(/backdrop-filter:[^;]+;/g, '') // Remove backdrop-filter
.replace(/var\(--shadowFilter\)[^;]*;/g, '') // Remove shadowFilter references
if (sanitizedRule !== rule) {
styles.sheet.insertRule(sanitizedRule, styles.sheet.cssRules.length)
styles.rules.push(sanitizedRule)
}
} catch (e2) {
console.error('Failed to insert even sanitized rule', e2)
}
}
}
const { lazyProcessFunc } = generateTheme( const { lazyProcessFunc } = generateTheme(
input, input,
{ {
onNewRule (rule, isLazy) { onNewRule (rule, isLazy) {
if (isLazy) { if (isLazy) {
insertRule(lazyStyles, rule) lazyStyles.addRule(rule)
} else { } else {
insertRule(eagerStyles, rule) eagerStyles.addRule(rule)
} }
}, },
onEagerFinished () { onEagerFinished () {
adoptStyleSheets([eagerStyles]) eagerStyles.ready = true
adoptStyleSheets()
onEagerFinish() onEagerFinish()
console.info('Eager part of theme finished, waiting for lazy part to finish to store cache') console.info('Eager part of theme finished, waiting for lazy part to finish to store cache')
}, },
onLazyFinished () { onLazyFinished () {
adoptStyleSheets([eagerStyles, lazyStyles]) eagerStyles.ready = true
adoptStyleSheets()
const cache = { engineChecksum: getEngineChecksum(), data: [eagerStyles.rules, lazyStyles.rules] } const cache = { engineChecksum: getEngineChecksum(), data: [eagerStyles.rules, lazyStyles.rules] }
onFinish(cache) onFinish(cache)
localforage.setItem('pleromafe-theme-cache', cache) localforage.setItem('pleromafe-theme-cache', cache)
@ -239,17 +235,18 @@ export const applyConfig = (input) => {
.filter(([, v]) => v) .filter(([, v]) => v)
.map(([k, v]) => `--${k}: ${v}`).join(';') .map(([k, v]) => `--${k}: ${v}`).join(';')
const styleEl = document.getElementById('theme-holder') const styleSheet = createStyleSheet('theme-holder')
const styleSheet = styleEl.sheet
styleSheet.insertRule(`:root { ${rules} }`, 'index-max') styleSheet.addRule(`:root { ${rules} }`)
// TODO find a way to make this not apply to theme previews // TODO find a way to make this not apply to theme previews
if (Object.prototype.hasOwnProperty.call(config, 'forcedRoundness')) { if (Object.prototype.hasOwnProperty.call(config, 'forcedRoundness')) {
styleSheet.insertRule(` *:not(.preview-block) { styleSheet.addRule(` *:not(.preview-block) {
--roundness: var(--forcedRoundness) !important; --roundness: var(--forcedRoundness) !important;
}`, 'index-max') }`)
} }
styleSheet.ready = true
adoptStyleSheets()
} }
export const getResourcesIndex = async (url, parser = JSON.parse) => { export const getResourcesIndex = async (url, parser = JSON.parse) => {

View file

@ -136,7 +136,7 @@ export default defineConfig(async ({ mode, command }) => {
'@ungap/event-target', '@ungap/event-target',
'lodash.merge', 'lodash.merge',
'body-scroll-lock', 'body-scroll-lock',
'@floatingghost/pinch-zoom-element' '@kazvmoe-infra/pinch-zoom-element'
] ]
}, },
css: { css: {

View file

@ -1446,13 +1446,6 @@
"@eslint/core" "^0.13.0" "@eslint/core" "^0.13.0"
levn "^0.4.1" levn "^0.4.1"
"@floatingghost/pinch-zoom-element@1.3.1":
version "1.3.1"
resolved "https://registry.yarnpkg.com/@floatingghost/pinch-zoom-element/-/pinch-zoom-element-1.3.1.tgz#5f327ad17ddf1f56777098aca088fdbf99cbd049"
integrity sha512-KnE7aBQdd/Fj1TzU5uzgwD9YAQ58DTMUks/PoTEBFW4zi0lBM9cN/j45wzcnzsT2VXG1S6qM7NMmq7NGm2//Fg==
dependencies:
pointer-tracker "^2.0.3"
"@fortawesome/fontawesome-common-types@6.7.2": "@fortawesome/fontawesome-common-types@6.7.2":
version "6.7.2" version "6.7.2"
resolved "https://registry.yarnpkg.com/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-6.7.2.tgz#7123d74b0c1e726794aed1184795dbce12186470" resolved "https://registry.yarnpkg.com/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-6.7.2.tgz#7123d74b0c1e726794aed1184795dbce12186470"
@ -1609,6 +1602,13 @@
"@jridgewell/resolve-uri" "^3.1.0" "@jridgewell/resolve-uri" "^3.1.0"
"@jridgewell/sourcemap-codec" "^1.4.14" "@jridgewell/sourcemap-codec" "^1.4.14"
"@kazvmoe-infra/pinch-zoom-element@1.3.0":
version "1.3.0"
resolved "https://registry.yarnpkg.com/@kazvmoe-infra/pinch-zoom-element/-/pinch-zoom-element-1.3.0.tgz#a5e35ab190f93d016b8a83f69004fc69a8e6b774"
integrity sha512-YIx1ZsCLyFB/xhJVMc81yLNJO/ZYveYGws0033ZPPyrFLgLwrQrkx79lC1xBIcWlWnvPioTVdmOpnBeTv/0zNw==
dependencies:
pointer-tracker "^2.0.3"
"@kazvmoe-infra/unicode-emoji-json@0.4.0": "@kazvmoe-infra/unicode-emoji-json@0.4.0":
version "0.4.0" version "0.4.0"
resolved "https://registry.yarnpkg.com/@kazvmoe-infra/unicode-emoji-json/-/unicode-emoji-json-0.4.0.tgz#555bab2f8d11db74820ef0a2fbe2805b17c22587" resolved "https://registry.yarnpkg.com/@kazvmoe-infra/unicode-emoji-json/-/unicode-emoji-json-0.4.0.tgz#555bab2f8d11db74820ef0a2fbe2805b17c22587"