Merge remote-tracking branch 'origin/develop' into 2-11-3-fixes

This commit is contained in:
Henry Jameson 2026-08-12 00:28:32 +03:00
commit a125a9af6c
117 changed files with 591 additions and 557 deletions

View file

@ -12,10 +12,7 @@ export const maybeShowChatNotification = (chat) => {
body: chat.lastMessage.content,
}
if (
chat.lastMessage.attachment &&
chat.lastMessage.attachment.type === 'image'
) {
if (chat.lastMessage.attachment?.type === 'image') {
opts.image = chat.lastMessage.attachment.preview_url
}

View file

@ -11,7 +11,7 @@ import { contrastRatio, convert, invertLightness } from 'chromatism'
* @param {Number} [b] - Blue component
*/
export const rgb2hex = (r, g, b) => {
if (r === null || typeof r === 'undefined') {
if (r === null || r === undefined) {
return undefined
}
// TODO: clean up this mess
@ -130,7 +130,7 @@ export const arithmeticBlend = (origin, value, operator) => {
* @returns {Object} sRGB of resulting color
*/
export const alphaBlend = (fg, fga, bg) => {
if (fga === 1 || typeof fga === 'undefined') {
if (fga === 1 || fga === undefined) {
return fg
}
@ -210,16 +210,16 @@ export const rgba2css = function (rgba) {
}
if (rgba !== null) {
if (rgba.r !== undefined && !isNaN(rgba.r)) {
if (rgba.r !== undefined && !Number.isNaN(rgba.r)) {
base.r = rgba.r
}
if (rgba.g !== undefined && !isNaN(rgba.g)) {
if (rgba.g !== undefined && !Number.isNaN(rgba.g)) {
base.g = rgba.g
}
if (rgba.b !== undefined && !isNaN(rgba.b)) {
if (rgba.b !== undefined && !Number.isNaN(rgba.b)) {
base.b = rgba.b
}
if (rgba.a !== undefined && !isNaN(rgba.a)) {
if (rgba.a !== undefined && !Number.isNaN(rgba.a)) {
base.a = rgba.a
}
} else {

View file

@ -62,8 +62,8 @@ export const parseUser = (data) => {
})
output.fields_text = data.fields.map((field) => {
return {
name: unescape(field.name.replace(/<[^>]*>/g, '')),
value: unescape(field.value.replace(/<[^>]*>/g, '')),
name: unescape(field.name.replaceAll(/<[^>]*>/g, '')),
value: unescape(field.value.replaceAll(/<[^>]*>/g, '')),
}
})
@ -191,7 +191,7 @@ export const parseUser = (data) => {
// Convert punycode to unicode for UI
output.screen_name_ui = output.screen_name
if (output.screen_name && output.screen_name.includes('@')) {
if (output.screen_name?.includes('@')) {
const parts = output.screen_name.split('@')
const unicodeDomain = punycode.toUnicode(parts[1])
if (unicodeDomain !== parts[1]) {

View file

@ -3,7 +3,7 @@ import { capitalize } from 'lodash'
function humanizeErrors(errors) {
return Object.entries(errors).reduce((errs, [k, val]) => {
const message = val.reduce((acc, message) => {
const key = capitalize(k.replace(/_/g, ' '))
const key = capitalize(k.replaceAll('_', ' '))
return acc + [key, message].join(' ') + '. '
}, '')
return [...errs, message]

View file

@ -43,7 +43,7 @@ export const fileTypeExt = (url) => {
}
export const fileMatchesSomeType = (types, file) =>
types.some((type) => fileType(file.mimetype) === type)
types.includes(fileType(file.mimetype))
const fileTypeService = {
fileType,

View file

@ -25,7 +25,7 @@ const visibleTypes = (notificationVisibility) => {
notificationVisibility.emojiReactions && 'pleroma:emoji_reaction',
notificationVisibility.reports && 'pleroma:report',
notificationVisibility.polls && 'poll',
].filter((_) => _)
].filter(Boolean)
}
const statusNotifications = new Set([
@ -95,9 +95,7 @@ export const filteredNotificationsFromStore = (
types,
) => {
// map is just to clone the array since sort mutates it and it causes some issues
const sortedNotifications = notificationsFromStore(store)
.map((_) => _)
.sort(sortById)
const sortedNotifications = notificationsFromStore(store).sort(sortById)
// TODO implement sorting elsewhere and make it optional
return sortedNotifications.filter((notification) =>
(types || visibleTypes(notificationVisibility)).includes(notification.type),
@ -175,13 +173,7 @@ export const prepareNotificationObject = (notification, i18n) => {
}
// Shows first attached non-nsfw image, if any. Should add configuration for this somehow...
if (
status &&
status.attachments &&
status.attachments.length > 0 &&
!status.nsfw &&
status.attachments[0].mimetype.startsWith('image/')
) {
if (!status.nsfw && status?.attachments?.[0]?.mimetype.startsWith('image/')) {
notifObj.image = status.attachments[0].url
}

View file

@ -25,13 +25,13 @@ const createRuffleService = () => {
script.src = '/static/ruffle/ruffle.js'
script.type = 'text/javascript'
script.onerror = (e) => {
reject(e)
reject(new Error('Ruffle script errorred', e))
}
script.onabort = (e) => {
reject(e)
reject(new Error('Ruffle script aborted', e))
}
script.oncancel = (e) => {
reject(e)
reject(new Error('Ruffle script cancelled', e))
}
script.onload = () => {
ruffleInstance = window.RufflePlayer

View file

@ -88,5 +88,5 @@ export const muteFilterHits = (muteFilters, status) => {
}
}
})
.filter((_) => _)
.filter(Boolean)
}

View file

@ -29,7 +29,7 @@ export const createStyleSheet = (id, priority = 1000) => {
addRule(rule) {
let newRule = rule
if (!CSS.supports?.('backdrop-filter', 'blur()')) {
newRule = newRule.replace(/backdrop-filter:[^;]+;/g, '') // Remove backdrop-filter
newRule = newRule.replaceAll(/backdrop-filter:[^;]+;/g, '') // Remove backdrop-filter
}
if (newRule.startsWith('::-webkit')) {
@ -44,7 +44,7 @@ export const createStyleSheet = (id, priority = 1000) => {
}
this.rules.push(
newRule.replace(/var\(--shadowFilter\)[^;]*;/g, ''), // Remove shadowFilter references
newRule.replaceAll(/var\(--shadowFilter\)[^;]*;/g, ''), // Remove shadowFilter references
)
},
}
@ -92,6 +92,11 @@ export const adoptStyleSheets = throttle(() => {
const EAGER_STYLE_ID = 'pleroma-eager-styles'
const LAZY_STYLE_ID = 'pleroma-lazy-styles'
export const hasInvalidCachedThemeRules = (data) =>
data
.flat()
.some((rule) => /--(?:mono)?font:\s*\[object Object\](?:;|$)/i.test(rule))
const generateTheme = (inputRuleset, callbacks, debug) => {
const {
onNewRule = () => {
@ -151,7 +156,8 @@ export const tryLoadCache = async () => {
if (
cache.engineChecksum === getEngineChecksum() &&
cache.checksum !== undefined &&
cache.checksum === useMergedConfigStore().mergedConfig.themeChecksum
cache.checksum === useMergedConfigStore().mergedConfig.themeChecksum &&
!hasInvalidCachedThemeRules(cache.data)
) {
const eagerStyles = createStyleSheet(EAGER_STYLE_ID, 10)
const lazyStyles = createStyleSheet(LAZY_STYLE_ID, 20)
@ -318,7 +324,7 @@ export const getResourcesIndex = async (url, parser = noop) => {
const resourceTransform = (resources) => {
return Object.entries(resources).map(([k, v]) => {
if (typeof v === 'object') {
return [k, () => Promise.resolve(v)]
return [k, () => v]
} else if (typeof v === 'string') {
return [
k,
@ -362,11 +368,9 @@ export const getResourcesIndex = async (url, parser = noop) => {
const total = [...custom, ...builtin]
if (total.length === 0) {
return Promise.reject(
new Error(
`Resource at ${url} and ${customUrl} completely unavailable. Panicking`,
),
throw new Error(
`Resource at ${url} and ${customUrl} completely unavailable. Panicking`,
)
}
return Promise.resolve(Object.fromEntries(total))
return Object.fromEntries(total)
}

View file

@ -1,7 +1,9 @@
/* global process */
function urlBase64ToUint8Array(base64String) {
const padding = '='.repeat((4 - (base64String.length % 4)) % 4)
const base64 = (base64String + padding).replace(/-/g, '+').replace(/_/g, '/')
const base64 = (base64String + padding)
.replaceAll('-', '+')
.replaceAll('_', '/')
const rawData = window.atob(base64)
return Uint8Array.from([...rawData].map((char) => char.codePointAt(0)))
@ -26,10 +28,8 @@ function getOrCreateServiceWorker() {
}
function subscribePush(registration, isEnabled, vapidPublicKey) {
if (!isEnabled)
return Promise.reject(new Error('Web Push is disabled in config'))
if (!vapidPublicKey)
return Promise.reject(new Error('VAPID public key is not found'))
if (!isEnabled) throw new Error('Web Push is disabled in config')
if (!vapidPublicKey) throw new Error('VAPID public key is not found')
const subscribeOptions = {
userVisibleOnly: false,
@ -38,10 +38,10 @@ function subscribePush(registration, isEnabled, vapidPublicKey) {
return registration.pushManager.subscribe(subscribeOptions)
}
function unsubscribePush(registration) {
async function unsubscribePush(registration) {
return registration.pushManager.getSubscription().then((subscription) => {
if (subscription === null) {
return Promise.resolve('No subscription')
return 'No subscription'
}
return subscription.unsubscribe()
})

View file

@ -165,7 +165,7 @@ export const getCssRules = (rules, debug) =>
header,
directives,
rule.component === 'Text' &&
rule.state.indexOf('faint') < 0 &&
!rule.state.includes('faint') &&
rule.directives.textNoCssColor !== 'yes'
? ' color: var(--text);'
: '',

View file

@ -24,7 +24,7 @@ export const getAllPossibleCombinations = (array) => {
const nonSelf = array.filter((x) => !selfSet.has(x))
return nonSelf.map((x) => [...self, x])
})
const flatCombos = newCombos.reduce((acc, x) => [...acc, ...x], [])
const flatCombos = newCombos.flat()
const uniqueComboStrings = new Set()
const uniqueCombos = flatCombos.map(sortBy).filter((x) => {
if (uniqueComboStrings.has(x.join())) {
@ -36,7 +36,7 @@ export const getAllPossibleCombinations = (array) => {
})
combos.push(uniqueCombos)
}
return combos.reduce((acc, x) => [...acc, ...x], [])
return combos.flat()
}
/**
@ -110,13 +110,6 @@ export const genericRuleToSelector =
let arraySelector = Array.isArray(selector) ? selector : [selector]
if (ignoreOutOfTreeSelector || liteMode)
arraySelector = [arraySelector[0]]
arraySelector
.sort((a) => {
if (a.startsWith(':')) return 1
if (/^[a-z]/.exec(a)) return -1
else return 0
})
.join('')
return arraySelector
})

View file

@ -191,14 +191,16 @@ export const convertTheme2To3 = (data) => {
newRules.push(rule)
if (rule.component === 'Button') {
newRules.push({ ...rule, component: 'ScrollbarElement' })
newRules.push({ ...rule, component: 'Tab' })
newRules.push({
...rule,
component: 'Tab',
state: ['active'],
directives: { opacity: 0 },
})
newRules.push(
{ ...rule, component: 'ScrollbarElement' },
{ ...rule, component: 'Tab' },
{
...rule,
component: 'Tab',
state: ['active'],
directives: { opacity: 0 },
},
)
}
if (rule.component === 'Panel') {
newRules.push({ ...rule, component: 'Post' })
@ -250,8 +252,10 @@ export const convertTheme2To3 = (data) => {
}
newRules.push(rule)
if (rule.component === 'Button') {
newRules.push({ ...rule, component: 'ScrollbarElement' })
newRules.push({ ...rule, component: 'Tab' })
newRules.push(
{ ...rule, component: 'ScrollbarElement' },
{ ...rule, component: 'Tab' },
)
}
})
return newRules
@ -349,16 +353,20 @@ export const convertTheme2To3 = (data) => {
newRules.push({ ...rule, parent: { component: 'Notification' } })
}
if (key === 'buttonPressed') {
newRules.push({ ...rule, state: ['toggled'] })
newRules.push({ ...rule, state: ['toggled', 'focus'] })
newRules.push({ ...rule, state: ['pressed', 'focus'] })
newRules.push({ ...rule, state: ['toggled', 'focus', 'hover'] })
newRules.push({ ...rule, state: ['pressed', 'focus', 'hover'] })
newRules.push(
{ ...rule, state: ['toggled'] },
{ ...rule, state: ['toggled', 'focus'] },
{ ...rule, state: ['pressed', 'focus'] },
{ ...rule, state: ['toggled', 'focus', 'hover'] },
{ ...rule, state: ['pressed', 'focus', 'hover'] },
)
}
if (rule.component === 'Button') {
newRules.push({ ...rule, component: 'ScrollbarElement' })
newRules.push({ ...rule, component: 'Tab' })
newRules.push(
{ ...rule, component: 'ScrollbarElement' },
{ ...rule, component: 'Tab' },
)
}
})
return newRules
@ -512,15 +520,17 @@ export const convertTheme2To3 = (data) => {
{ ...newRule, component: 'Tab' },
{ ...newRule, component: 'ScrollbarElement' },
]
if (newRule.state?.indexOf('toggled') >= 0) {
rules.push({ ...newRule, state: [...newRule.state, 'focused'] })
rules.push({ ...newRule, state: [...newRule.state, 'hover'] })
rules.push({
...newRule,
state: [...newRule.state, 'hover', 'focused'],
})
if (newRule.state?.includes('toggled')) {
rules.push(
{ ...newRule, state: [...newRule.state, 'focused'] },
{ ...newRule, state: [...newRule.state, 'hover'] },
{
...newRule,
state: [...newRule.state, 'hover', 'focused'],
},
)
}
if (newRule.state?.indexOf('hover') >= 0) {
if (newRule.state?.includes('hover')) {
rules.push({ ...newRule, state: [...newRule.state, 'focused'] })
}
return rules
@ -559,9 +569,9 @@ export const convertTheme2To3 = (data) => {
const flatExtRules = extendedRules
.filter(Boolean)
.reduce((acc, x) => [...acc, ...x], [])
.flat()
.filter(Boolean)
.reduce((acc, x) => [...acc, ...x], [])
.flat()
return [
generateRoot(),

View file

@ -244,10 +244,7 @@ export const OPACITIES = Object.entries(SLOT_INHERITANCE).reduce((acc, [k]) => {
...acc,
[opacity]: {
defaultValue: DEFAULT_OPACITY[opacity] || 1,
affectedSlots: [
...((acc[opacity] && acc[opacity].affectedSlots) || []),
k,
],
affectedSlots: [...(acc[opacity]?.affectedSlots || []), k],
},
}
} else {
@ -413,7 +410,7 @@ export const getColors = (sourceColors, sourceOpacity) =>
outputColor.a = Number(
opacityOverriden
? sourceOpacity[opacitySlot]
: (OPACITIES[opacitySlot] || {}).defaultValue,
: OPACITIES[opacitySlot]?.defaultValue,
)
}
}
@ -460,7 +457,7 @@ export const generatePreset = (input) => {
return composePreset(
colors,
generateRadii(input),
generateShadows(input, colors.theme.colors, colors.mod),
generateShadows(input, colors.theme.colors),
generateFonts(input),
)
}

View file

@ -262,7 +262,7 @@ export const init = ({
...r,
})),
)
.reduce((acc, arr) => [...acc, ...arr], []),
.flat(),
...inputRuleset,
].map((rule) => {
normalizeCombination(rule)
@ -690,11 +690,11 @@ export const init = ({
.map((combination) => ['normal', ...combination])
.filter((combo) => {
// Optimization: filter out some hard-coded combinations that don't make sense
if (combo.indexOf('disabled') >= 0) {
if (combo.includes('disabled')) {
return !(
combo.indexOf('hover') >= 0 ||
combo.indexOf('focused') >= 0 ||
combo.indexOf('pressed') >= 0
combo.includes('hover') ||
combo.includes('focused') ||
combo.includes('pressed')
)
}
return true
@ -705,13 +705,13 @@ export const init = ({
.map((variant) => {
return stateCombinations.map((state) => ({ variant, state }))
})
.reduce((acc, x) => [...acc, ...x], [])
.flat()
stateVariantCombination.forEach((combination) => {
combination.component = component.name
combination.lazy = component.lazy || parent?.lazy
combination.parent = parent
if (!liteMode && combination.state.indexOf('hover') >= 0) {
if (!liteMode && combination.state.includes('hover')) {
combination.lazy = true
}

View file

@ -47,7 +47,7 @@ const highlightStyle = (prefs) => {
const highlightClass = (user) => {
return (
'USER____' + user.screen_name?.replace(/\./g, '_').replace(/@/g, '_AT_')
'USER____' + user.screen_name?.replaceAll('.', '_').replaceAll('@', '_AT_')
)
}