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

@ -0,0 +1,36 @@
when:
- event: push
branch: ${CI_REPO_DEFAULT_BRANCH}
- event: manual
branch: ${CI_REPO_DEFAULT_BRANCH}
depends_on:
- test
- test-e2e
labels:
platform: linux/amd64
memory: 'high'
steps:
build:
image: docker.io/node:20-alpine
commands:
- apk add --no-cache zip git
- yarn --frozen-lockfile
- yarn build
- if [ "${CI_PIPELINE_EVENT}" = "push" ] || [ "${CI_PIPELINE_EVENT}" = "manual" ]; then zip -9qr ${CI_REPO_DEFAULT_BRANCH}.zip dist/; fi
upload-artifacts:
image: docker.io/woodpeckercommunity/plugin-gitea-package:0.5.0
settings:
user:
from_secret: pleroma-ci-user
password:
from_secret: pleroma-ci-password
update: true
owner: 'pleroma'
package_name: 'pleroma-fe-builds'
package_version: ${CI_REPO_DEFAULT_BRANCH}
file_source: ./${CI_REPO_DEFAULT_BRANCH}.zip
file_name: latest.zip

View file

@ -6,38 +6,12 @@ when:
- event: manual
branch: ${CI_REPO_DEFAULT_BRANCH}
depends_on:
- test
- test-e2e
labels:
platform: linux/amd64
memory: 'high'
steps:
build:
image: docker.io/node:20-alpine
commands:
- apk add --no-cache zip git
- yarn --frozen-lockfile
- yarn build
- if [ "${CI_PIPELINE_EVENT}" = "push" ] || [ "${CI_PIPELINE_EVENT}" = "manual" ]; then zip -9qr ${CI_REPO_DEFAULT_BRANCH}.zip dist/; fi
upload-artifacts:
image: docker.io/woodpeckercommunity/plugin-gitea-package:0.5.0
when:
- event: push
branch: ${CI_REPO_DEFAULT_BRANCH}
- event: manual
branch: ${CI_REPO_DEFAULT_BRANCH}
settings:
user:
from_secret: pleroma-ci-user
password:
from_secret: pleroma-ci-password
update: true
owner: 'pleroma'
package_name: 'pleroma-fe-builds'
package_version: ${CI_REPO_DEFAULT_BRANCH}
file_source: ./${CI_REPO_DEFAULT_BRANCH}.zip
file_name: latest.zip

View file

@ -0,0 +1,45 @@
when:
- event: push
branch: ${CI_REPO_DEFAULT_BRANCH}
- event: manual
branch: ${CI_REPO_DEFAULT_BRANCH}
labels:
platform: linux/amd64
memory: 'high'
depends_on:
- build
- test
- test-e2e
clone:
- name: git
image: woodpeckerci/plugin-git
settings:
depth: 0
partial: false
variables:
script_file_entrypoint: &script_file_entrypoint
- /bin/sh
- -c
- 'printf "%s" "$CI_SCRIPT" | base64 -d > /tmp/ci-script.sh && /bin/sh -xe /tmp/ci-script.sh'
steps:
generate-coverage:
image: mcr.microsoft.com/playwright:v1.61.0-jammy
environment:
FF_NETWORK_PER_BUILD: "true"
PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: "1"
entrypoint: *script_file_entrypoint
commands:
- yarn --frozen-lockfile
- yarn unit-ci-coverage
code-analysis:
image: sonarsource/sonar-scanner-cli:11
environment:
SONAR_TOKEN:
from_secret: sonarqube-token
entrypoint: sonar-scanner

View file

@ -10,6 +10,10 @@ labels:
platform: linux/amd64
memory: 'high'
depends_on:
- build
- test
variables:
artifacts_uploader_settings: &artifacts_uploader_settings
user:

View file

@ -10,6 +10,9 @@ labels:
platform: linux/amd64
memory: 'high'
depends_on:
- build
variables:
artifacts_uploader_settings: &artifacts_uploader_settings
user:
@ -45,20 +48,6 @@ steps:
exit 1
fi
code-analysis:
image: sonarsource/sonar-scanner-cli:11
when:
- event: push
branch: ${CI_REPO_DEFAULT_BRANCH}
- event: manual
branch: ${CI_REPO_DEFAULT_BRANCH}
environment:
SONAR_TOKEN:
from_secret: sonarqube-token
entrypoint: *script_file_entrypoint
commands:
- sonar-scanner
upload-artifacts:
image: docker.io/woodpeckercommunity/plugin-gitea-package:0.5.0
when:

View file

@ -1,12 +1,14 @@
# Pleroma-FE
# Pleroma-FE
> Highly-customizable frontend designed for Pleroma.
![screenshot](./image-1.png)
[![Pipeline Status](https://ci.pleroma.com/api/badges/2/status.svg)](https://ci.pleroma.com/repos/2) [![Coverage](https://sonarqube.pleroma.dev/api/project_badges/measure?project=Pleroma-FE&metric=coverage&token=sqb_f8a5ec836caede0e2c6c62a9bfe66e62a89e2b01)](https://sonarqube.pleroma.dev/dashboard?id=Pleroma-FE) [![Maintainability Rating](https://sonarqube.pleroma.dev/api/project_badges/measure?project=Pleroma-FE&metric=software_quality_maintainability_rating&token=sqb_f8a5ec836caede0e2c6c62a9bfe66e62a89e2b01)](https://sonarqube.pleroma.dev/dashboard?id=Pleroma-FE) [![Reliability Rating](https://sonarqube.pleroma.dev/api/project_badges/measure?project=Pleroma-FE&metric=software_quality_reliability_rating&token=sqb_f8a5ec836caede0e2c6c62a9bfe66e62a89e2b01)](https://sonarqube.pleroma.dev/dashboard?id=Pleroma-FE) [![Security Rating](https://sonarqube.pleroma.dev/api/project_badges/measure?project=Pleroma-FE&metric=software_quality_security_rating&token=sqb_f8a5ec836caede0e2c6c62a9bfe66e62a89e2b01)](https://sonarqube.pleroma.dev/dashboard?id=Pleroma-FE) <a href="https://translate.pleroma.social/engage/pleroma/"><img src="https://translate.pleroma.social/widget/pleroma/pleroma-fe/svg-badge.svg" alt="Translation status"></a>
# For Translators
To translate Pleroma-FE, use our weblate server: https://translate.pleroma.social/. If you need to add your language it should be added as a json file in [src/i18n/](https://git.pleroma.social/pleroma/pleroma-fe/src/src/i18n/) folder and added in a list within [src/i18n/languages.js](https://git.pleroma.social/pleroma/pleroma-fe/src/src/i18n/languages.js).
To translate Pleroma-FE, use our weblate server: https://translate.pleroma.social/. If you need to add your language it should be added as a json file in [src/i18n/](https://git.pleroma.social/pleroma/pleroma-fe/src/src/i18n/) folder and added in a list within [src/i18n/languages.js](https://git.pleroma.social/pleroma/pleroma-fe/src/src/i18n/languages.js).
Pleroma-FE will set your language by your browser locale, but you can change language in settings.
@ -32,9 +34,14 @@ yarn unit
# For Contributors:
You can make local build proxy requests to specific instance with an environment variable `VITE_PROXY_TARGET`, generally it is a good idea to also set port for that specific proxy, if you intend on testing multiple instances, i.e.:
```bash
yarn && PORT=8080 VITE_PROXY_TARGET=https://coolinstance.tld yarn dev
```
You can create file `/config/local.json` (see [example](https://git.pleroma.social/pleroma/pleroma-fe/src/config/local.example.json)) to enable some convenience dev options:
* `target`: makes local dev server redirect to some existing instance's BE instead of local BE, useful for testing things in near-production environment and searching for real-life use-cases.
* `staticConfigPreference`: makes FE's `/static/config.json` take preference of BE-served `/api/pleroma/frontend_configurations`. Only works in dev mode.
FE Build process also leaves current commit hash in global variable `___pleromafe_commit_hash` so that you can easily see which pleroma-fe commit instance is running, also helps pinpointing which commit was used when FE was bundled into BE.

View file

@ -7,7 +7,9 @@ export const getCommitHash = () => {
} else {
try {
return childProcess
.execSync('git rev-parse --short HEAD')
.execSync(
'PATH=/usr/bin:/bin:/usr/local/bin:/sbin:/usr/sbin git rev-parse --short HEAD',
)
.toString()
.trim()
} catch (e) {

View file

@ -35,7 +35,7 @@ const getAllAccessibleAnnotations = async (projectRoot) => {
}),
)
)
.filter((k) => k)
.filter(Boolean)
.join(',\n')
return `

View file

@ -3,7 +3,7 @@ import emojis from '@kazvmoe-infra/unicode-emoji-json/data-by-group.json' with {
type: 'json',
}
Object.keys(emojis).map((k) => {
Object.keys(emojis).forEach((k) => {
emojis[k].forEach((e) => {
delete e.unicode_version
delete e.emoji_version

View file

@ -0,0 +1 @@
Fixed server-side domain mutes rendering/api calls

View file

@ -0,0 +1 @@
Fix Theme 2 fonts falling back to serif after upgrade

View file

@ -0,0 +1 @@
Fixed status index approxmiation in timeline rendering for dynamically changing viewport geometries

View file

@ -8,7 +8,8 @@
"dev": "node build/update-emoji.js && vite dev",
"build": "node build/update-emoji.js && vite build",
"unit": "node build/update-emoji.js && vitest --run --coverage",
"unit-ci": "node build/update-emoji.js && vitest --run --coverage --browser.headless",
"unit-ci": "node build/update-emoji.js && vitest --run --browser.headless",
"unit-ci-coverage": "node build/update-emoji.js && vitest --run --coverage --browser.headless",
"unit:watch": "node build/update-emoji.js && vitest --coverage",
"e2e:pw": "playwright test --config test/e2e-playwright/playwright.config.mjs",
"e2e": "sh ./tools/e2e/run.sh",

View file

@ -1,3 +1,5 @@
sonar.projectKey=Pleroma-FE
sonar.qualitygatesonar.host.url.wait=true
sonar.host.url=https://sonarqube.pleroma.dev
sonar.sources=src,build,tools
sonar.tests=test

View file

@ -237,10 +237,10 @@ export const changeStatusScope = ({
credentials,
}) => {
const payload = {}
if (typeof sensitive !== 'undefined') {
if (sensitive !== undefined) {
payload['sensitive'] = sensitive
}
if (typeof visibility !== 'undefined') {
if (visibility !== undefined) {
payload['visibility'] = visibility
}
@ -260,15 +260,15 @@ export const announcementToPayload = ({
}) => {
const payload = { content }
if (typeof startsAt !== 'undefined') {
if (startsAt !== undefined) {
payload.starts_at = startsAt ? new Date(startsAt).toISOString() : null
}
if (typeof endsAt !== 'undefined') {
if (endsAt !== undefined) {
payload.ends_at = endsAt ? new Date(endsAt).toISOString() : null
}
if (typeof allDay !== 'undefined') {
if (allDay !== undefined) {
payload.all_day = allDay
}

View file

@ -18,7 +18,7 @@ export const chats = ({ credentials }) =>
url: PLEROMA_CHATS_URL,
credentials,
}).then(({ data }) => ({
data: data.map(parseChat).filter((c) => c),
data: data.map(parseChat).filter(Boolean),
}))
export const getOrCreateChat = ({ accountId, credentials }) =>
@ -40,7 +40,7 @@ export const chatMessages = ({
method: 'GET',
credentials,
}).then(({ data }) => ({
data: data.map(parseChatMessage).filter((c) => c),
data: data.map(parseChatMessage).filter(Boolean),
}))
}

View file

@ -37,11 +37,7 @@ export const paramsString = (params = {}) => {
arrays.forEach(([k, array]) => {
array.forEach((v) => {
if (
typeof v === 'object' ||
typeof v === 'function' ||
typeof v === 'undefined'
)
if (typeof v === 'object' || typeof v === 'function' || v === undefined)
throw new TypeError('Array param cannot contain non-primitives!')
})
})

View file

@ -29,9 +29,8 @@ const Announcement = {
currentUser: (state) => state.users.currentUser,
}),
canEditAnnouncement() {
return (
this.currentUser &&
this.currentUser.privileges.has('announcements_manage_announcements')
return this.currentUser?.privileges.has(
'announcements_manage_announcements',
)
},
content() {

View file

@ -33,9 +33,8 @@ const AnnouncementsPage = {
return useAnnouncementsStore().announcements
},
canPostAnnouncement() {
return (
this.currentUser &&
this.currentUser.privileges.has('announcements_manage_announcements')
return this.currentUser?.privileges.has(
'announcements_manage_announcements',
)
},
},

View file

@ -165,16 +165,16 @@ const Attachment = {
useMediaViewerStore().setCurrentMedia(this.attachment)
},
onEdit(event) {
this.edit && this.edit(this.attachment, event)
this.edit?.(this.attachment, event)
},
onRemove() {
this.remove && this.remove(this.attachment)
this.remove?.(this.attachment)
},
onShiftUp() {
this.shiftUp && this.shiftUp(this.attachment)
this.shiftUp?.(this.attachment)
},
onShiftDn() {
this.shiftDn && this.shiftDn(this.attachment)
this.shiftDn?.(this.attachment)
},
stopFlash() {
this.$refs.flash.closePlayer()

View file

@ -1,9 +1,9 @@
<template>
<div
v-if="isMessage"
:id="`chatmessage-${message.id}`"
class="chat-message-wrapper"
:class="[classnames, { 'hovered-message-chain': hoveredMessageChain }]"
:id="`chatmessage-${message.id}`"
@mouseover="onHover(true)"
@mouseleave="onHover(false)"
>
@ -40,7 +40,10 @@
</template>
</i18n-t>
</StatusPopover>
<span v-else class="reply-label">
<span
v-else
class="reply-label"
>
{{ $t('status.broken_reply') }}
</span>
</template>
@ -83,7 +86,10 @@
:user="author"
/>
</UserPopover>
<div v-else class="avatar-spacer" />
<div
v-else
class="avatar-spacer"
/>
</div>
<div class="chat-message-inner">
<div class="message-bubble-wrapper">
@ -98,7 +104,6 @@
@mouseenter="hovered = true"
@mouseleave="hovered = false"
>
<StatusActionButtons
v-if="isStatus"
class="chat-message-toolbar"
@ -112,9 +117,9 @@
@toggle-replying="$emit('replyRequested', message)"
/>
<div
v-else
class="chat-message-toolbar"
:class="{ '-visible': hovered || menuOpened }"
v-else
>
<Popover
trigger="click"
@ -221,7 +226,10 @@
v-if="isStatus && repliedTo"
class="reply-indicator"
>
<FAIcon class="icon" icon="reply" />
<FAIcon
class="icon"
icon="reply"
/>
</div>
<div class="end-spacer" />
</div>
@ -232,7 +240,10 @@
v-else
class="chat-message-date-separator"
>
<ChatMessageDate :date="chatItem.date" :show-time="chatItem.isTime" />
<ChatMessageDate
:date="chatItem.date"
:show-time="chatItem.isTime"
/>
</div>
</template>

View file

@ -7,7 +7,7 @@
:previous-item="getPreviousItem(index)"
:hovered-message-chain="chatItem.messageChainId === hoveredMessageChainId"
:focused="chatItem.id === focusedId"
:repliedTo="chatItem.id === repliedId"
:replied-to="chatItem.id === repliedId"
@hover="onMessageHover"
@delete="onMessageDelete"
@reply-requested="onReplyRequested"

View file

@ -97,7 +97,7 @@ const Chat = {
mounted() {
window.addEventListener('resize', this.handleResize)
window.addEventListener('scroll', this.handleScroll)
if (typeof document.hidden !== 'undefined') {
if (document.hidden !== undefined) {
document.addEventListener(
'visibilitychange',
this.handleVisibilityChange,
@ -112,7 +112,7 @@ const Chat = {
unmounted() {
window.removeEventListener('scroll', this.handleScroll)
window.removeEventListener('resize', this.handleResize)
if (typeof document.hidden !== 'undefined')
if (document.hidden !== undefined)
document.removeEventListener(
'visibilitychange',
this.handleVisibilityChange,

View file

@ -24,7 +24,7 @@
v-if="messages[0]?.summary_raw_html"
:html="messages[0].summary_raw_html"
:emoji="messages[0].emojis"
/>
/>
<template v-else>
{{ $t('timeline.conversation') }}
</template>

View file

@ -11,11 +11,11 @@
{{ label }}
</label>
<Checkbox
v-if="typeof fallback !== 'undefined' && showOptionalCheckbox && !hideOptionalCheckbox"
v-if="fallback !== undefined && showOptionalCheckbox && !hideOptionalCheckbox"
:model-value="present"
:disabled="disabled"
class="opt"
@update:model-value="updateValue(typeof modelValue === 'undefined' ? fallback : undefined)"
@update:model-value="updateValue(modelValue === undefined ? fallback : undefined)"
/>
<div
class="input color-input-field"

View file

@ -62,7 +62,7 @@ const sortAndFilterConversation = (conversation, statusoid) => {
} else {
conversation = filter(conversation, (status) => status.type !== 'retweet')
}
return conversation.filter((_) => _).sort(sortById)
return conversation.filter(Boolean).sort(sortById)
}
const conversation = {
@ -239,9 +239,9 @@ const conversation = {
depth,
},
walk(forest, forest[id], depth + 1, processed),
].reduce((a, b) => a.concat(b), [])
].flat()
})
.reduce((a, b) => a.concat(b), [])
.flat()
const linearized = walk(
threads.forest,
@ -307,9 +307,9 @@ const conversation = {
(tl, cur) =>
tl.filter(
(k) =>
this.getReplies(cur.id)
!this.getReplies(cur.id)
.map((v) => v.id)
.indexOf(k.id) === -1,
.includes(k.id),
),
this.conversation,
)
@ -375,7 +375,7 @@ const conversation = {
return !!(this.expanded || this.isPage)
},
hiddenStyle() {
const height = (this.status && this.status.virtualHeight) || '120px'
const height = this.status?.virtualHeight || '120px'
return this.virtualHidden ? { height } : {}
},
threadDisplayStatus() {

View file

@ -14,9 +14,9 @@
v-if="conversation[0]?.summary_raw_html"
:html="conversation[0].summary_raw_html"
:emoji="conversation[0].emojis"
/>
/>
<template v-else>
{{ $t('timeline.conversation') }}
{{ $t('timeline.conversation') }}
</template>
</h1>
<button

View file

@ -45,8 +45,7 @@ export default {
data: () => ({
searchBarHidden: true,
supportsMask:
window.CSS &&
window.CSS.supports &&
window.CSS?.supports &&
(window.CSS.supports('mask-size', 'contain') ||
window.CSS.supports('-webkit-mask-size', 'contain') ||
window.CSS.supports('-moz-mask-size', 'contain') ||

View file

@ -42,9 +42,7 @@ const EditStatusModal = {
},
isFormVisible(val) {
if (val) {
this.$nextTick(
() => this.$el && this.$el.querySelector('textarea').focus(),
)
this.$nextTick(() => this.$el?.querySelector('textarea').focus())
}
},
},

View file

@ -188,8 +188,8 @@ const EmojiInput = {
}
return {
names: names.filter((k) => k),
keywords: keywords.filter((k) => k),
names: names.filter(Boolean),
keywords: keywords.filter(Boolean),
}
}
},

View file

@ -79,7 +79,7 @@ export const suggestUsers = ({ dispatch, state }) => {
const userSearch = (query) => dispatch('searchUsers', { query })
const debounceUserSearch = (query) => {
cancelUserSearch && cancelUserSearch()
cancelUserSearch?.()
return new Promise((resolve, reject) => {
timeout = setTimeout(() => {
userSearch(query).then(resolve).catch(reject)

View file

@ -57,7 +57,7 @@ const maybeLocalizedKeywords = (emoji, languages, nameLocalizer) => {
languages.forEach((lang) => {
const keywords = emoji.annotations[lang]?.keywords || []
const name = emoji.annotations[lang]?.name
res.push(...keywords.concat([name]).filter((k) => k))
res.push(...keywords.concat([name]).filter(Boolean))
})
}
return res
@ -408,7 +408,7 @@ const EmojiPicker = {
isFirstRow: index === 0,
})),
)
.reduce((a, c) => a.concat(c), [])
.flat()
},
languages() {
return ensureFinalFallback(

View file

@ -44,7 +44,7 @@ const Flash = {
})
},
closePlayer() {
this.ruffleInstance && this.ruffleInstance.remove()
this.ruffleInstance?.remove()
this.player = false
this.$emit('playerClosed')
},

View file

@ -35,7 +35,7 @@ export default {
'sans-serif',
'monospace',
...(this.options || []),
].filter((_) => _),
].filter(Boolean),
}
},
methods: {

View file

@ -2,7 +2,7 @@
<div class="font-control">
<div class="setting-item">
<Checkbox
v-if="typeof fallback !== 'undefined'"
v-if="fallback !== undefined"
:id="name + '-o'"
class="font-checkbox setting-control setting-label"
:model-value="present"

View file

@ -50,7 +50,7 @@ const ImageCropper = {
},
readFile() {
const fileInput = this.$refs.input
if (fileInput.files != null && fileInput.files[0] != null) {
if (fileInput?.files?.[0]) {
this.file = fileInput.files[0]
const reader = new window.FileReader()
reader.onload = (e) => {

View file

@ -59,12 +59,12 @@ const ListsNew = {
membersUsers() {
return [...this.membersUserIds, ...this.addedUserIds]
.map((userId) => this.findUser(userId))
.filter((user) => user)
.filter(Boolean)
},
searchUsers() {
return this.searchUserIds
.map((userId) => this.findUser(userId))
.filter((user) => user)
.filter(Boolean)
},
...mapState({
currentUser: (state) => state.users.currentUser,

View file

@ -75,13 +75,11 @@ const MentionLink = {
},
computed: {
user() {
return (
this.url && this.$store && this.$store.getters.findUserByUrl(this.url)
)
return this.url && this.$store?.getters.findUserByUrl(this.url)
},
isYou() {
// FIXME why user !== currentUser???
return this.user && this.user.id === this.currentUser.id
return this.user?.id === this.currentUser.id
},
userName() {
return this.user && this.userNameFullUi.split('@')[0]
@ -94,10 +92,10 @@ const MentionLink = {
)
},
userNameFull() {
return this.user && this.user.screen_name
return this.user?.screen_name
},
userNameFullUi() {
return this.user && this.user.screen_name_ui
return this.user?.screen_name_ui
},
highlightData() {
return this.highlight[this.user?.screen_name]

View file

@ -11,7 +11,7 @@ import { useInstanceStore } from 'src/stores/instance.js'
*/
const toInstanceReasonObject = (instances, info, key) => {
return instances.map((instance) => {
if (info[key] && info[key][instance] && info[key][instance].reason) {
if (info[key]?.[instance]?.reason) {
return { instance, reason: info[key][instance].reason }
}
return { instance, reason: '' }

View file

@ -15,8 +15,7 @@ export const filterNavigation = (
if (!isFederating && set.has('federating')) return false
if (!currentUser && isPrivate && set.has('!private')) return false
if (!currentUser && !(anon || anonRoute)) return false
if ((!currentUser || !currentUser.locked) && set.has('lockedUser'))
return false
if (!currentUser?.locked && set.has('lockedUser')) return false
if (!hasChats && set.has('chats')) return false
if (!hasAnnouncements && set.has('announcements')) return false
if (!supportsBubbleTimeline && set.has('supportsBubbleTimeline'))

View file

@ -11,7 +11,7 @@
{{ label || $t('settings.style.themes3.editor.opacity') }}
</label>
<Checkbox
v-if="typeof fallback !== 'undefined'"
v-if="fallback !== undefined"
:model-value="present"
:disabled="disabled"
class="opt"

View file

@ -39,13 +39,13 @@ export default {
return storePoll || {}
},
options() {
return (this.poll && this.poll.options) || []
return this.poll?.options || []
},
expiresAt() {
return (this.poll && this.poll.expires_at) || null
return this.poll?.expires_at || null
},
expired() {
return (this.poll && this.poll.expired) || false
return this.poll?.expired || false
},
expirationLabel() {
if (useMergedConfigStore().mergedConfig.useAbsoluteTimeFormat) {

View file

@ -25,6 +25,10 @@ export default {
},
emits: ['update:modelValue'],
computed: {
// Model value
options() {
return pollFallback(this.modelValue, 'options')
},
pollType: {
get() {
return pollFallback(this.modelValue, 'pollType')
@ -36,14 +40,6 @@ export default {
})
},
},
options: {
get() {
return pollFallback(this.modelValue, 'options')
},
set(newVal) {
this.$emit('update:modelValue', { ...this.modelValue, options: newVal })
},
},
expiryAmount: {
get() {
return pollFallback(this.modelValue, 'expiryAmount')
@ -66,6 +62,8 @@ export default {
})
},
},
// Configuration
pollLimits() {
return useInstanceStore().limits.pollLimits
},
@ -116,19 +114,34 @@ export default {
},
addOption() {
if (this.options.length < this.maxOptions) {
this.options = [...this.options, '']
this.$emit('update:modelValue', {
...this.modelValue,
options: [...this.options, ''],
})
return true
}
return false
},
deleteOption(index) {
if (this.options.length > 2) {
this.options.splice(index, 1)
this.options = this.options
const options = [...this.options]
options.splice(index, 1)
this.$emit('update:modelValue', {
...this.modelValue,
options,
})
}
},
updateOption(index, value) {
this.options = this.options
const options = [...this.options]
options[index] = value
this.$emit('update:modelValue', {
...this.modelValue,
options,
})
},
convertExpiryToUnit(unit, amount) {
// Note: we want seconds and not milliseconds

View file

@ -11,14 +11,15 @@
<div class="input-container">
<input
:id="`poll-${index}`"
v-model="options[index]"
:model-value="options[index]"
size="1"
class="input poll-option-input"
type="text"
:placeholder="$t('polls.option')"
:maxlength="maxLength"
:aria-label="$t('polls.option')"
@keydown.enter.stop.prevent="nextOption(index)"
@change="updateOption"
@change="(e) => updateOption(index, e.target.value)"
>
</div>
<button
@ -69,6 +70,7 @@
class="input expiry-amount hide-number-spinner"
:min="minExpirationInCurrentUnit"
:max="maxExpirationInCurrentUnit"
:aria-label="$t('polls.expiry_amount')"
@change="expiryAmountChange"
>
{{ ' ' }}
@ -77,6 +79,7 @@
unstyled="true"
class="expiry-unit"
@change="expiryAmountChange"
:aria-label="$t('polls.expiry_unit')"
>
<option
v-for="unit in expiryUnits"

View file

@ -129,9 +129,7 @@ const Popover = {
// Popover will be anchored around this element, trigger ref is the container, so
// its children are what are inside the slot. Expect only one v-slot:trigger.
const anchorEl =
this.anchorEl ||
(this.$refs.trigger && this.$refs.trigger.children[0]) ||
this.$el
this.anchorEl || this.$refs.trigger?.children[0] || this.$el
// SVGs don't have offsetWidth/Height, use fallback
const anchorHeight = anchorEl.offsetHeight || anchorEl.clientHeight
const anchorWidth = anchorEl.offsetWidth || anchorEl.clientWidth
@ -155,8 +153,7 @@ const Popover = {
// Minor optimization, don't call a slow reflow call if we don't have to
const parentScreenBox =
this.boundTo &&
(this.boundTo.x === 'container' || this.boundTo.y === 'container') &&
(this.boundTo?.x === 'container' || this.boundTo?.y === 'container') &&
this.containerBoundingClientRect()
const margin = this.margin || {}
@ -164,7 +161,7 @@ const Popover = {
// What are the screen bounds for the popover? Viewport vs container
// when using viewport, using default margin values to dodge the navbar
const xBounds =
this.boundTo && this.boundTo.x === 'container'
this.boundTo?.x === 'container'
? {
min: parentScreenBox.left + (margin.left || 0),
max: parentScreenBox.right - (margin.right || 0),
@ -175,7 +172,7 @@ const Popover = {
}
const yBounds =
this.boundTo && this.boundTo.y === 'container'
this.boundTo?.y === 'container'
? {
min: parentScreenBox.top + (margin.top || 0),
max: parentScreenBox.bottom - (margin.bottom || 0),
@ -247,12 +244,12 @@ const Popover = {
if (bottomBoundary + content.offsetHeight > yBounds.max) usingTop = true
if (topBoundary - content.offsetHeight < yBounds.min) usingTop = false
const yOffset = (this.offset && this.offset.y) || 0
const yOffset = this.offset?.y || 0
translateY = usingTop
? topBoundary - yOffset - content.offsetHeight
: bottomBoundary + yOffset
const xOffset = (this.offset && this.offset.x) || 0
const xOffset = this.offset?.x || 0
translateX = origin.x + horizOffset + xOffset
} else {
// Default to whatever user wished with placement prop
@ -268,12 +265,12 @@ const Popover = {
if (rightBoundary + content.offsetWidth > xBounds.max) usingLeft = true
if (leftBoundary - content.offsetWidth < xBounds.min) usingLeft = false
const xOffset = (this.offset && this.offset.x) || 0
const xOffset = this.offset?.x || 0
translateX = usingLeft
? leftBoundary - xOffset - content.offsetWidth
: rightBoundary + xOffset
const yOffset = (this.offset && this.offset.y) || 0
const yOffset = this.offset?.y || 0
translateY = origin.y + vertOffset + yOffset
}
@ -298,7 +295,7 @@ const Popover = {
}, 0)
const wasHidden = this.hidden
this.hidden = false
this.parentPopover && this.parentPopover.onChildPopoverState(this, true)
this.parentPopover?.onChildPopoverState(this, true)
if (this.trigger === 'click' || this.stayOnClick) {
document.addEventListener('click', this.onClickOutside)
}
@ -316,7 +313,7 @@ const Popover = {
if (this.disabled) return
if (!this.hidden) this.$emit('close')
this.hidden = true
this.parentPopover && this.parentPopover.onChildPopoverState(this, false)
this.parentPopover?.onChildPopoverState(this, false)
if (this.trigger === 'click') {
document.removeEventListener('click', this.onClickOutside)
}
@ -366,7 +363,7 @@ const Popover = {
onClickOutside(e) {
if (this.disableClickOutside) return
if (this.hidden) return
if (this.$refs.content && this.$refs.content.contains(e.target)) return
if (this.$refs.content?.contains(e.target)) return
if (this.$el.contains(e.target)) return
if (this.childrenShown.size > 0) return
this.hidePopover()

View file

@ -316,7 +316,7 @@ const PostStatusForm = {
},
// -Edit
isEdit() {
return typeof this.statusId !== 'undefined' && this.statusId.trim() !== ''
return this.statusId !== undefined && this.statusId.trim() !== ''
},
// -Reply
isReply() {
@ -378,8 +378,8 @@ const PostStatusForm = {
set(value) {
if (value) {
this.newStatus.quote = {}
this.newStatus.quote.thread = value
this.newStatus.quote.id = value ? this.repliedStatus.id : ''
this.newStatus.quote.thread = true
this.newStatus.quote.id = this.repliedStatus.id
} else {
this.newStatus.quote = null
}
@ -509,9 +509,7 @@ const PostStatusForm = {
// Error handling
pollContentError() {
return (
this.pollFormVisible && this.newStatus.poll && this.newStatus.poll.error
)
return this.pollFormVisible && this.newStatus.poll?.error
},
// Featureset detection
@ -622,7 +620,7 @@ const PostStatusForm = {
this.newStatus.quote = null
this.newStatus.nsfw = this.defaultNewStatus.nsfw
this.newStatus.mediaDescriptions = {}
this.$refs.mediaUpload && this.$refs.mediaUpload.clearFile()
this.$refs.mediaUpload?.clearFile()
if (this.preserveFocus) {
this.$nextTick(() => {
this.$refs.textarea.focus()
@ -812,7 +810,7 @@ const PostStatusForm = {
}
},
fileDrop(e) {
if (e.dataTransfer && e.dataTransfer.types.includes('Files')) {
if (e.dataTransfer?.types.includes('Files')) {
e.preventDefault() // allow dropping text like before
this.dropFiles = e.dataTransfer.files
clearTimeout(this.dropStopTimeout)
@ -829,7 +827,7 @@ const PostStatusForm = {
},
fileDrag(e) {
e.dataTransfer.dropEffect = this.uploadFileLimitReached ? 'none' : 'copy'
if (e.dataTransfer && e.dataTransfer.types.includes('Files')) {
if (e.dataTransfer?.types.includes('Files')) {
clearTimeout(this.dropStopTimeout)
this.showDropIcon = 'show'
}
@ -962,6 +960,7 @@ const PostStatusForm = {
// Quote
toggleQuoteForm() {
// This is for the "attach quote" button
if (this.newStatus.quote?.thread) return
if (!this.hasQuote) {
this.newStatus.quote = {}
this.newStatus.quote.thread = false

View file

@ -1,8 +1,8 @@
<template>
<div
v-if="initialized"
ref="form"
class="post-status-form"
v-if="initialized"
>
<form
autocomplete="off"
@ -176,12 +176,12 @@
<input
v-if="mentionsLine"
:value="mentionsLineReadOnly ? mentionsString : newStatus.mentionsLine"
@change="onMentionsLineUpdate"
type="text"
:placeholder="$t('post_status.mentions_line')"
:disabled="mentionsLineReadOnly || (posting && !optimisticPosting)"
size="1"
class="input mentions-input form-post-mentions unstyled"
@change="onMentionsLineUpdate"
>
<EmojiInput
ref="emoji-input"
@ -234,7 +234,7 @@
ref="scopeSelector"
:show-all="showAllScopes"
:user-default="userDefaultScope"
:original-scope="newStatus.visibility"
:original-scope="repliedStatus?.visibility"
:initial-scope="newStatus.visibility"
@change="changeVis"
/>
@ -271,14 +271,14 @@
<PollForm
v-if="pollsAvailable"
ref="pollForm"
:visible="pollFormVisible"
v-model="newStatus.poll"
:visible="pollFormVisible"
/>
<QuoteForm
v-if="quotingAvailable"
:id="newStatus.quote?.id"
ref="quoteForm"
:visible="quoteFormVisible"
:id="newStatus.quote?.id"
:url="newStatus.quote?.url"
@update:url="url => newStatus.quote.url = url"
@update:id="id => newStatus.quote.id = id"

View file

@ -40,9 +40,7 @@ const PostStatusModal = {
},
isFormVisible(val) {
if (val) {
this.$nextTick(
() => this.$el && this.$el.querySelector('textarea').focus(),
)
this.$nextTick(() => this.$el?.querySelector('textarea').focus())
}
},
},

View file

@ -102,7 +102,7 @@ export default {
type: 'statuses',
})
.then((data) => {
if (data?.statuses && data.statuses.length === 1) {
if (data?.statuses?.length === 1) {
this.$emit('update:id', data.statuses[0].id)
} else {
this.handleError(true)

View file

@ -10,6 +10,7 @@
size="1"
class="input"
:placeholder="$t('post_status.quote_url')"
:aria-label="$t('post_status.quote_url')"
>
</div>
<Quote

View file

@ -11,7 +11,7 @@
{{ label }}
</label>
<input
v-if="typeof fallback !== 'undefined'"
v-if="fallback !== undefined"
:id="name + '-o'"
:aria-labelledby="name + '-label'"
class="input -checkbox opt visible-for-screenreader-only"
@ -20,7 +20,7 @@
@change="$emit('update:modelValue', !present ? fallback : undefined)"
>
<label
v-if="typeof fallback !== 'undefined'"
v-if="fallback !== undefined"
class="opt-l"
:for="name + '-o'"
:aria-hidden="true"

View file

@ -128,7 +128,7 @@ const registration = {
this.user.captcha_answer_data = this.captcha.answer_data
if (this.user.language) {
this.user.language = localeService.internalToBackendLocaleMulti(
this.user.language.filter((k) => k),
this.user.language.filter(Boolean),
)
}
@ -154,7 +154,7 @@ const registration = {
})
},
replaceNewlines(str) {
return str.replace(/\s*\n\s*/g, ' \n')
return str.replaceAll(/\s*\n\s*/g, ' \n')
},
},
}

View file

@ -181,7 +181,7 @@ export default {
}
// Processor to use with html_tree_converter
const processItem = (item, index, array, what) => {
const processItem = (item, index, array) => {
// Handle text nodes - just add emoji
if (typeof item === 'string') {
const emptyText = item.trim() === ''
@ -251,18 +251,14 @@ export default {
return ['', [mentionsLinePadding, renderImage(opener)], '']
} else if (Tag === 'a' && this.handleLinks) {
// replace mentions with MentionLink
if (fullAttrs.class && fullAttrs.class.includes('mention')) {
if (fullAttrs.class?.includes('mention')) {
// Handling mentions here
return renderMention(attrs, children)
} else {
currentMentions = null
}
} else if (Tag === 'span') {
if (
this.handleLinks &&
fullAttrs.class &&
fullAttrs.class.includes('h-card')
) {
if (this.handleLinks && fullAttrs.class?.includes('h-card')) {
return ['', children.map(processItem), '']
}
}
@ -281,7 +277,7 @@ export default {
// Processor for back direction (for finding "last" stuff, just easier this way)
let encounteredTextReverse = false
const processItemReverse = (item, index, array, what) => {
const processItemReverse = (item, index, array) => {
// Handle text nodes - just add emoji
if (typeof item === 'string') {
const emptyText = item.trim() === ''
@ -300,7 +296,7 @@ export default {
const attrs = getAttrs(opener, () => true)
// should only be this
if (
(fullAttrs.class && fullAttrs.class.includes('hashtag')) || // Pleroma style
fullAttrs.class?.includes('hashtag') || // Pleroma style
fullAttrs.rel === 'tag' // Mastodon style
) {
return renderHashtag(attrs, children, encounteredTextReverse)
@ -385,13 +381,13 @@ export default {
x ? 'mfm-spinX' : null,
y ? 'mfm-spinY' : null,
'mfm-spin',
].filter((a) => a)[0]
].filter(Boolean)[0]
const direction = [
alternate ? 'alternate' : null,
left ? 'reverse' : null,
'normal',
].filter((a) => a)[0]
].filter(Boolean)[0]
newAttrs.style = [
`animation-name: ${anim}`,
@ -479,7 +475,7 @@ export default {
>
{this.collapse
? pass2.map((x) => {
if (typeof x === 'string') return x.replace(/\n/g, ' ')
if (typeof x === 'string') return x.replaceAll('\n', ' ')
if (!Array.isArray(x)) return x
return x.map((y) => (y.type === 'br' ? ' ' : y))
})
@ -547,8 +543,8 @@ export const preProcessPerLine = (html, greentext) => {
(string.includes('&gt;') || string.includes('&lt;'))
) {
const cleanedString = string
.replace(/<[^>]+?>/gi, '') // remove all tags
.replace(/@\w+/gi, '') // remove mentions (even failed ones)
.replaceAll(/<[^>]+?>/gi, '') // remove all tags
.replaceAll(/@\w+/gi, '') // remove mentions (even failed ones)
.trim()
if (cleanedString.startsWith('&gt;')) {
return `<span class='greentext'>${string}</span>`

View file

@ -11,7 +11,7 @@
{{ label }}
</label>
<Checkbox
v-if="typeof fallback !== 'undefined'"
v-if="fallback !== undefined"
:model-value="present"
:disabled="disabled"
class="opt"

View file

@ -122,7 +122,7 @@ const Search = {
return 'statuses'
},
lastHistoryRecord(hashtag) {
return hashtag.history && hashtag.history[0]
return hashtag.history?.[0]
},
},
}

View file

@ -122,7 +122,7 @@ const EmojiTab = {
return this.refreshPackList()
} else {
this.displayError(resp.error)
return Promise.reject(resp)
throw new Error(resp)
}
})
.then(() => {
@ -139,7 +139,7 @@ const EmojiTab = {
return this.refreshPackList()
} else {
this.displayError(resp.error)
return Promise.reject(resp)
throw new Error(resp)
}
})
.then(() => {
@ -239,7 +239,7 @@ const EmojiTab = {
return this.refreshPackList()
} else {
this.displayError(resp.error)
return Promise.reject(resp)
throw new Error(resp)
}
})
.then(() => {
@ -259,7 +259,7 @@ const EmojiTab = {
return this.refreshPackList()
} else {
this.displayError(resp.error)
return Promise.reject(resp)
throw new Error(resp)
}
})
.then(() => {
@ -280,7 +280,7 @@ const EmojiTab = {
return this.refreshPackList()
} else {
this.displayError(resp.error)
return Promise.reject(resp)
throw new Error(resp)
}
})
.then(() => {
@ -300,7 +300,7 @@ const EmojiTab = {
sortPackFiles(nameOfPack) {
// Sort by key
const sorted = Object.keys(this.knownPacks[nameOfPack].files)
.sort()
.sort((a, b) => a.localeCompare(b))
.reduce((acc, key) => {
if (key.length === 0) return acc
acc[key] = this.knownPacks[nameOfPack].files[key]

View file

@ -262,7 +262,7 @@ export default {
.then((resp) => {
if (resp.error !== undefined) {
this.$emit('displayError', resp.error)
return Promise.reject(resp.error)
throw new Error(resp.error)
}
return resp.json()

View file

@ -28,12 +28,12 @@ export default {
methods: {
...Setting.methods,
getValue(e) {
if (!this.truncate === 1) {
if (this.truncate === 1) {
return Number.parseInt(e.target.value)
} else if (this.truncate > 1) {
return Math.trunc(e.target.value / this.truncate) * this.truncate
}
return parseFloat(e.target.value)
return Number.parseFloat(e.target.value)
},
},
}

View file

@ -180,7 +180,7 @@ export default {
[
'admin_dash',
'temp_overrides',
...this.canonPath.map((p) => p.replace(/\./g, '_DOT_')),
...this.canonPath.map((p) => p.replaceAll('.', '_DOT_')),
'label',
].join('.'),
)
@ -199,7 +199,7 @@ export default {
[
'admin_dash',
'temp_overrides',
...this.canonPath.map((p) => p.replace(/\./g, '_DOT_')),
...this.canonPath.map((p) => p.replaceAll('.', '_DOT_')),
'description',
].join('.'),
)

View file

@ -67,7 +67,10 @@ export default {
return this.$t(['settings', 'units', this.unitSet, value].join('.'))
},
updateValue(e) {
this.configSink(this.path, parseFloat(e.target.value) + this.stateUnit)
this.configSink(
this.path,
Number.parseFloat(e.target.value) + this.stateUnit,
)
},
updateUnit(e) {
let value = this.stateValue

View file

@ -236,7 +236,7 @@ const AppearanceTab = {
},
stylePalettes() {
const ruleset = useInterfaceStore().styleDataUsed || []
if (!ruleset?.length === 0) return
if (ruleset.length === 0) return
const meta = ruleset.find((x) => x.component === '@meta')
const result = ruleset
.filter((x) => x.component.startsWith('@palette'))
@ -257,7 +257,7 @@ const AppearanceTab = {
const result = {
name: `${meta.directives.name || this.$t('settings.style.themes3.palette.imported')}: ${variant}`,
key: `style.${variant.toLowerCase().replace(/ /g, '_')}`,
key: `style.${variant.toLowerCase().replaceAll(' ', '_')}`,
bg,
fg,
text,
@ -277,7 +277,7 @@ const AppearanceTab = {
return !window.IntersectionObserver
},
instanceWallpaper() {
useInstanceStore().instanceIdentity.background
return useInstanceStore().instanceIdentity.background
},
instanceWallpaperUsed() {
return (

View file

@ -1,5 +1,4 @@
import { mapActions, mapState } from 'pinia'
import { v4 as uuidv4 } from 'uuid'
import { mapState } from 'pinia'
import Checkbox from 'src/components/checkbox/checkbox.vue'
import Select from 'src/components/select/select.vue'
@ -12,7 +11,6 @@ import UnitSetting from '../helpers/unit_setting.vue'
import { useInstanceStore } from 'src/stores/instance.js'
import { useInstanceCapabilitiesStore } from 'src/stores/instance_capabilities.js'
import { useSyncConfigStore } from 'src/stores/sync_config.js'
const ClutterTab = {
components: {
@ -33,120 +31,6 @@ const ClutterTab = {
store.instanceIdentity.showInstanceSpecificPanel &&
store.instanceIdentity.instanceSpecificPanelContent,
}),
...mapState(useSyncConfigStore, {
muteFilters: (store) =>
Object.entries(store.prefsStorage.simple.muteFilters),
muteFiltersObject: (store) => store.prefsStorage.simple.muteFilters,
}),
},
methods: {
...mapActions(useSyncConfigStore, [
'setSimplePrefAndSave',
'unsetSimplePrefAndSave',
'pushSyncConfig',
]),
getDatetimeLocal(timestamp) {
const date = new Date(timestamp)
const fmt = new Intl.NumberFormat('en-US', { minimumIntegerDigits: 2 })
const datetime = [
date.getFullYear(),
'-',
fmt.format(date.getMonth() + 1),
'-',
fmt.format(date.getDate()),
'T',
fmt.format(date.getHours()),
':',
fmt.format(date.getMinutes()),
].join('')
return datetime
},
checkRegexValid(id) {
const filter = this.muteFiltersObject[id]
if (filter.type !== 'regexp') return true
if (filter.type !== 'user_regexp') return true
const { value } = filter
let valid = true
try {
new RegExp(value)
} catch {
valid = false
console.error('Invalid RegExp: ' + value)
}
return valid
},
createFilter(
filter = {
type: 'word',
value: '',
name: 'New Filter',
enabled: true,
expires: null,
hide: false,
},
) {
const newId = uuidv4()
filter.order = this.muteFilters.length + 2
this.muteFiltersDraftObject[newId] = filter
this.setSimplePrefAndSave({ path: 'muteFilters.' + newId, value: filter })
this.pushSyncConfig()
},
exportFilter(id) {
this.exportedFilter = { ...this.muteFiltersDraftObject[id] }
delete this.exportedFilter.order
this.filterExporter.exportData()
},
importFilter() {
this.filterImporter.importData()
},
copyFilter(id) {
const filter = { ...this.muteFiltersDraftObject[id] }
const newId = uuidv4()
this.muteFiltersDraftObject[newId] = filter
this.setSimplePrefAndSave({ path: 'muteFilters.' + newId, value: filter })
this.pushSyncConfig()
},
deleteFilter(id) {
delete this.muteFiltersDraftObject[id]
this.unsetSimplePrefAndSave({ path: 'muteFilters.' + id, value: null })
this.pushSyncConfig()
},
purgeExpiredFilters() {
this.muteFiltersExpired.forEach(([id]) => {
delete this.muteFiltersDraftObject[id]
this.unsetSimplePrefAndSave({ path: 'muteFilters.' + id, value: null })
})
this.pushSyncConfig()
},
updateFilter(id, field, value) {
const filter = { ...this.muteFiltersDraftObject[id] }
if (field === 'expires-never') {
if (!value) {
const offset = 1000 * 60 * 60 * 24 * 14 // 2 weeks
const date = Date.now() + offset
filter.expires = date
} else {
filter.expires = null
}
} else if (field === 'expires') {
const parsed = Date.parse(value)
filter.expires = parsed.valueOf()
} else {
filter[field] = value
}
this.muteFiltersDraftObject[id] = filter
this.muteFiltersDraftDirty[id] = true
},
saveFilter(id) {
this.setSimplePrefAndSave({
path: 'muteFilters.' + id,
value: this.muteFiltersDraftObject[id],
})
this.pushSyncConfig()
this.muteFiltersDraftDirty[id] = false
},
},
// Updating nested properties
watch: {

View file

@ -190,21 +190,24 @@ const FilteringTab = {
}
return valid
},
createFilter(
filter = {
createFilter(filter) {
const newId = uuidv4()
const newFilter = {
type: 'word',
value: '',
name: 'New Filter',
enabled: true,
expires: null,
hide: false,
},
) {
const newId = uuidv4()
...filter,
}
filter.order = this.muteFilters.length + 2
this.muteFiltersDraftObject[newId] = filter
this.setSimplePrefAndSave({ path: 'muteFilters.' + newId, value: filter })
newFilter.order = this.muteFilters.length + 2
this.muteFiltersDraftObject[newId] = newFilter
this.setSimplePrefAndSave({
path: 'muteFilters.' + newId,
value: newFilter,
})
},
exportFilter(id) {
this.exportedFilter = { ...this.muteFiltersDraftObject[id] }

View file

@ -153,7 +153,6 @@
</div>
</template>
<template #item="{item}">
{{ item }}
<DomainMuteCard :domain="item" />
</template>
<template #empty>

View file

@ -319,7 +319,7 @@ export default {
return useInterfaceStore().themeDataUsed
},
shadowsAvailable() {
return Object.keys(DEFAULT_SHADOWS).sort()
return Object.keys(DEFAULT_SHADOWS).sort((a, b) => a.localeCompare(b))
},
currentShadowOverriden: {
get() {
@ -346,7 +346,7 @@ export default {
},
},
currentShadowFallback() {
return (this.previewTheme.shadows || {})[this.shadowSelected]
return this.previewTheme.shadows?.[this.shadowSelected]
},
currentShadow: {
get() {
@ -425,8 +425,8 @@ export default {
this.dismissWarning()
const version =
origin === 'localStorage' && !theme.colors ? 'l1' : fileVersion
const snapshotEngineVersion = (theme || {}).themeEngineVersion
const themeEngineVersion = (source || {}).themeEngineVersion || 2
const snapshotEngineVersion = theme?.themeEngineVersion
const themeEngineVersion = source?.themeEngineVersion || 2
const versionsMatch = themeEngineVersion === CURRENT_VERSION
const sourceSnapshotMismatch =
theme !== undefined &&
@ -611,7 +611,7 @@ export default {
*/
normalizeLocalState(theme, version = 0, source, forceSource = false) {
let input
if (typeof source !== 'undefined') {
if (source !== undefined) {
if (forceSource || source?.themeEngineVersion === CURRENT_VERSION) {
input = source
version = source.themeEngineVersion

View file

@ -181,14 +181,14 @@
name="accentColor"
:fallback="previewTheme.colors?.link"
:label="$t('settings.accent')"
:show-optional-checkbox="typeof linkColorLocal !== 'undefined'"
:show-optional-checkbox="linkColorLocal !== undefined"
/>
<ColorInput
v-model="linkColorLocal"
name="linkColor"
:fallback="previewTheme.colors?.accent"
:label="$t('settings.links')"
:show-optional-checkbox="typeof accentColorLocal !== 'undefined'"
:show-optional-checkbox="accentColorLocal !== undefined"
/>
<ContrastRatio :contrast="previewContrast.bgLink" />
</div>

View file

@ -61,7 +61,7 @@ const SideDrawer = {
this.toggleDrawer,
)
if (this.currentUser && this.currentUser.locked) {
if (this.currentUser?.locked) {
this.$store.dispatch('startFetchingFollowRequests')
}
},

View file

@ -128,8 +128,7 @@ const Status = {
computed: {
showReasonMutedThread() {
return (
(this.status.thread_muted ||
(this.status.reblog && this.status.reblog.thread_muted)) &&
(this.status.thread_muted || this.status.reblog?.thread_muted) &&
!this.inConversation
)
},
@ -263,7 +262,7 @@ const Status = {
this.muteFilterHits.length > 0 ? 'filtered' : null,
this.muteBotStatuses && this.botStatus ? 'bot' : null,
this.muteSensitiveStatuses && this.sensitiveStatus ? 'nsfw' : null,
].filter((_) => _)
].filter(Boolean)
},
muteLocalized() {
if (this.muteReasons.length === 0) return null

View file

@ -20,9 +20,9 @@
:get-component="getComponent"
:close="() => { /* no-op */ }"
:do-action="doAction"
@emoji-picker-shown="onEmojiPickerShown"
:default-button-style="useDefaultButtons"
:hide-label="hideLabels"
@emoji-picker-shown="onEmojiPickerShown"
/>
<button
v-if="showPin && currentUser"

View file

@ -147,16 +147,15 @@ const StatusBody = {
return this.status.attachments.map((file) => file.type)
},
collapsedStatus() {
return this.status.raw_html.replace(/(\n|<br\s?\/?>)/g, ' ')
return this.status.raw_html.replaceAll('(\n|<br\s?\/?>)', ' ')
},
...mapState(useMergedConfigStore, ['mergedConfig']),
},
mounted() {
this.status.attentions &&
this.status.attentions.forEach((attn) => {
const { id } = attn
this.$store.dispatch('fetchUserIfMissing', id)
})
this.status.attentions?.forEach((attn) => {
const { id } = attn
this.$store.dispatch('fetchUserIfMissing', id)
})
},
methods: {
onParseReady(event) {
@ -168,7 +167,7 @@ const StatusBody = {
.filter((mention) => !mention.notifying)
.forEach((mention) => {
const { content, url } = mention
const cleanedString = content.replace(/<[^>]+?>/gi, '') // remove all tags
const cleanedString = content.replaceAll(/<[^>]+?>/gi, '') // remove all tags
if (!cleanedString.startsWith('@')) return
const handle = cleanedString.slice(1)
const host = url.replace(/^https?:\/\//, '').replace(/\/.+?$/, '')

View file

@ -51,7 +51,7 @@ const StillImage = {
}
const image = this.$refs.src
if (!image) return
this.imageLoadHandler && this.imageLoadHandler(image)
this.imageLoadHandler?.(image)
const canvas = this.$refs.canvas
if (!canvas) return
const width = image.naturalWidth
@ -61,7 +61,7 @@ const StillImage = {
canvas.getContext('2d').drawImage(image, 0, 0, width, height)
},
onError() {
this.imageLoadError && this.imageLoadError()
this.imageLoadError?.()
},
},
watch: {

View file

@ -111,7 +111,11 @@ export default {
type="button"
role="tab"
>
<img src={props.image} title={props['image-tooltip']} />
<img
src={props.image}
alt={props['image-tooltip']}
title={props['image-tooltip']}
/>
{props.label ? '' : props.label}
</button>
</div>

View file

@ -263,7 +263,10 @@ const Timeline = {
// Start from approximating the index of some visible status by using the
// the center of the screen on the timeline.
let approxIndex = Math.floor(statuses.length * (centerOfScreen / height))
let approxIndex = Math.min(
Math.floor(statuses.length * (centerOfScreen / height)),
statuses.length - 1,
)
let err = statuses[approxIndex].getBoundingClientRect().y
// if we have a previous scroll index that can be used, test if it's

View file

@ -196,7 +196,7 @@ export default {
},
computed: {
escapedNewBio() {
return ldEscape(this.newBio).replace(/\n/g, '<br>')
return ldEscape(this.newBio).replaceAll('\n', '<br>')
},
somethingToSave() {
if (this.newName !== this.user.name_unescaped) return true

View file

@ -1,7 +1,7 @@
#!/usr/bin/env node
const arg = process.argv[2]
if (typeof arg === 'undefined') {
if (arg === undefined) {
console.info('This is a very simple and tiny tool that checks en.json with any other language and')
console.info('outputs all the things present in english but missing in foreign language.')
console.info('')

View file

@ -242,6 +242,8 @@
"single_choice": "Single choice",
"multiple_choices": "Multiple choices",
"expiry": "Poll age",
"expiry_unit": "Expiry unit",
"expiry_amount": "Expiry amount",
"expires_at": "Poll ends {0}",
"expires_in": "Poll ends in {0}",
"expired": "Poll ended {0} ago",

View file

@ -18,9 +18,9 @@ const hasLanguageFile = (code) => languages.includes(code)
const languageFileMap = import.meta.glob(['./*.json', '!./en.json'])
const loadLanguageFile = (code) => {
const loadLanguageFile = async (code) => {
const jsonName = langCodeToJsonName(code)
if (jsonName === 'en') return Promise.resolve({ default: enMessages })
if (jsonName === 'en') return { default: enMessages }
return languageFileMap[`./${jsonName}.json`]()
}

View file

@ -74,7 +74,7 @@ export default function createPersistedState({
if (saveImmedeatelyActions.includes(mutation.type)) {
setState(key, reducer(cloneDeep(state), paths), storage).then(
(success) => {
if (typeof success !== 'undefined') {
if (success !== undefined) {
if (
mutation.type === 'setOption' ||
mutation.type === 'setCurrentUser'
@ -185,7 +185,7 @@ export const piniaPersistPlugin =
}
const fallbackValue = await storage.getItem(vuexKey)
if (fallbackValue && fallbackValue[id]) {
if (fallbackValue?.[id]) {
console.info(`Migrating ${id} store data from vuex to pinia`)
const res = fallbackValue[id]
await storage.setItem(key, res)
@ -198,7 +198,7 @@ export const piniaPersistPlugin =
const setState = (state) => {
if (!loadedGuard.loaded) {
console.info('waiting for old state to be loaded...')
return Promise.reject()
throw new Error('Waiting')
} else {
return storage.setItem(key, state)
}

View file

@ -325,7 +325,7 @@ const api = {
const token = state.wsToken
if (
useInstanceCapabilitiesStore().shoutAvailable &&
typeof token !== 'undefined' &&
token !== undefined &&
state.socket === null
) {
const socket = new Socket('/socket', { params: { token } })
@ -336,7 +336,7 @@ const api = {
}
},
disconnectFromSocket({ commit, state }) {
state.socket && state.socket.disconnect()
state.socket?.disconnect()
commit('setSocket', null)
},
},

View file

@ -49,8 +49,8 @@ const emptyTl = (userId = 0) => ({
visibleStatuses: [],
visibleStatusesObject: {},
newStatusCount: 0,
maxId: 0,
minId: 0,
maxId: '',
minId: '',
minVisibleId: 0,
loading: false,
followers: [],
@ -64,7 +64,7 @@ export const defaultState = () => ({
scrobblesNextFetch: {},
allStatusesObject: {},
conversationsObject: {},
maxId: 0,
maxId: '',
favorites: new Set(),
timelines: {
mentions: emptyTl(),
@ -134,7 +134,7 @@ const sortById = (a, b) => {
const sortTimeline = (timeline) => {
timeline.visibleStatuses = timeline.visibleStatuses.sort(sortById)
timeline.statuses = timeline.statuses.sort(sortById)
timeline.minVisibleId = (last(timeline.visibleStatuses) || {}).id
timeline.minVisibleId = last(timeline.visibleStatuses)?.id
return timeline
}
@ -222,11 +222,11 @@ const addNewStatuses = (
const newer =
timeline &&
(maxNew > timelineObject.maxId || timelineObject.maxId === 0) &&
(maxNew > timelineObject.maxId || timelineObject.maxId === '') &&
statuses.length > 0
const older =
timeline &&
(minNew < timelineObject.minId || timelineObject.minId === 0) &&
(minNew < timelineObject.minId || timelineObject.minId === '') &&
statuses.length > 0
if (!noIdUpdate && newer) {
@ -525,7 +525,7 @@ export const mutations = {
},
addRepeats(state, { id, rebloggedByUsers, currentUser }) {
const newStatus = state.allStatusesObject[id]
newStatus.rebloggedBy = rebloggedByUsers.filter((_) => _)
newStatus.rebloggedBy = rebloggedByUsers.filter(Boolean)
// repeats stats can be incorrect based on polling condition, let's update them using the most recent data
newStatus.repeat_num = newStatus.rebloggedBy.length
newStatus.repeated = !!newStatus.rebloggedBy.find(
@ -534,7 +534,7 @@ export const mutations = {
},
addFavs(state, { id, favoritedByUsers, currentUser }) {
const newStatus = state.allStatusesObject[id]
newStatus.favoritedBy = favoritedByUsers.filter((_) => _)
newStatus.favoritedBy = favoritedByUsers.filter(Boolean)
// favorites stats can be incorrect based on polling condition, let's update them using the most recent data
newStatus.fave_num = newStatus.favoritedBy.length
newStatus.favorited = !!newStatus.favoritedBy.find(
@ -879,7 +879,7 @@ const statuses = {
store.commit('addNewUsers', data.accounts)
store.commit(
'addNewUsers',
data.statuses.map((s) => s.user).filter((u) => u),
data.statuses.map((s) => s.user).filter(Boolean),
)
store.dispatch('addNewStatuses', {
statuses: data.statuses,

View file

@ -75,13 +75,13 @@ const mergeArrayLength = (oldValue, newValue) => {
}
}
const getNotificationPermission = () => {
const getNotificationPermission = async () => {
const Notification = window.Notification
if (!Notification) return Promise.resolve(null)
if (!Notification) return null
if (Notification.permission === 'default')
return Notification.requestPermission()
return Promise.resolve(Notification.permission)
return Notification.permission
}
const blockUser = (store, args) => {
@ -271,7 +271,7 @@ export const mutations = {
state.currentUser.blockIds = blockIds
},
addBlockId(state, blockId) {
if (state.currentUser.blockIds.indexOf(blockId) === -1) {
if (state.currentUser.blockIds.includes(blockId)) {
state.currentUser.blockIds.push(blockId)
}
},
@ -285,7 +285,7 @@ export const mutations = {
state.currentUser.muteIdsMaxId = muteIdsMaxId
},
addMuteId(state, muteId) {
if (state.currentUser.muteIds.indexOf(muteId) === -1) {
if (state.currentUser.muteIds.includes(muteId)) {
state.currentUser.muteIds.push(muteId)
}
},
@ -293,7 +293,7 @@ export const mutations = {
state.currentUser.domainMutes = domainMutes
},
addDomainMute(state, domain) {
if (state.currentUser.domainMutes.indexOf(domain) === -1) {
if (state.currentUser.domainMutes.includes(domain)) {
state.currentUser.domainMutes.push(domain)
}
},
@ -385,12 +385,12 @@ const users = {
mutations,
getters,
actions: {
fetchUserIfMissing(store, id) {
async fetchUserIfMissing(store, id) {
const user = store.getters.findUser(id)
if (!user) {
return store.dispatch('fetchUser', id)
} else {
return Promise.resolve(user)
return user
}
},
updateUserAdminData(store, { userAdminData }) {
@ -640,7 +640,7 @@ const users = {
},
addNewNotifications(store, { notifications }) {
const users = map(notifications, 'from_profile')
const targetUsers = map(notifications, 'target').filter((_) => _)
const targetUsers = map(notifications, 'target').filter(Boolean)
const notificationIds = notifications.map((_) => _.id)
store.commit('addNewUsers', users)
store.commit('addNewUsers', targetUsers)

View file

@ -148,6 +148,14 @@
background-image:
linear-gradient(to bottom, var(--background), var(--background)),
linear-gradient(to bottom, var(--__panel-background), var(--__panel-background));
overflow: hidden;
text-overflow: ellipsis;
.title {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
--_shadow: var(--shadow);

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_')
)
}

View file

@ -61,7 +61,7 @@ export const useChatsStore = defineStore('chats', {
addNewChats(chats) {
window.vuex.commit(
'addNewUsers',
chats.map((k) => k.account).filter((k) => k),
chats.map((k) => k.account).filter(Boolean),
)
chats.forEach((updatedChat) => {
@ -69,8 +69,7 @@ export const useChatsStore = defineStore('chats', {
if (chat) {
const isNewMessage =
(chat.lastMessage && chat.lastMessage.id) !==
(updatedChat.lastMessage && updatedChat.lastMessage.id)
chat.lastMessage?.id !== updatedChat.lastMessage?.id
chat.lastMessage = updatedChat.lastMessage
chat.unread = updatedChat.unread
chat.updated_at = updatedChat.updated_at

View file

@ -120,23 +120,19 @@ export const useEmojiStore = defineStore('emoji', {
}, {})
},
standardEmojiList(state) {
return (
SORTED_EMOJI_GROUP_IDS.map((groupId) =>
(this.emoji[groupId] || []).map((k) =>
injectAnnotations(k, this.unicodeEmojiAnnotations),
),
).reduce((a, b) => a.concat(b), []) ?? []
)
return SORTED_EMOJI_GROUP_IDS.map((groupId) =>
(this.emoji[groupId] || []).map((k) =>
injectAnnotations(k, this.unicodeEmojiAnnotations),
),
).flat()
},
standardEmojiGroupList(state) {
return (
SORTED_EMOJI_GROUP_IDS.map((groupId) => ({
id: groupId,
emojis: (this.emoji[groupId] || []).map((k) =>
injectAnnotations(k, this.unicodeEmojiAnnotations),
),
})) ?? []
)
return SORTED_EMOJI_GROUP_IDS.map((groupId) => ({
id: groupId,
emojis: (this.emoji[groupId] || []).map((k) =>
injectAnnotations(k, this.unicodeEmojiAnnotations),
),
}))
},
},
actions: {
@ -146,7 +142,7 @@ export const useEmojiStore = defineStore('emoji', {
async getStaticEmoji() {
try {
// See build/emojis_plugin for more details
const values = (await import('/src/assets/emoji.json')).default
const values = (await import('src/assets/emoji.json')).default
const emoji = Object.keys(values).reduce((res, groupId) => {
res[groupId] = values[groupId].map((e) => ({
@ -231,7 +227,7 @@ export const useEmojiStore = defineStore('emoji', {
.then((allPacks) => {
// Sort by key
return Object.keys(allPacks)
.sort()
.sort((a, b) => a.localeCompare(b))
.reduce((acc, key) => {
if (key.length === 0) return acc
acc[key] = allPacks[key]

Some files were not shown because too many files have changed in this diff Show more