Merge remote-tracking branch 'origin/develop' into timed-user-mutes

This commit is contained in:
Henry Jameson 2025-07-09 15:57:23 +03:00
commit 385f921c41
72 changed files with 1336 additions and 851 deletions

View file

@ -11,6 +11,11 @@ const getSWMessagesAsText = async () => {
}
const projectRoot = dirname(dirname(fileURLToPath(import.meta.url)))
const swEnvName = 'virtual:pleroma-fe/service_worker_env'
const swEnvNameResolved = '\0' + swEnvName
const getDevSwEnv = () => `self.serviceWorkerOption = { assets: [] };`
const getProdSwEnv = ({ assets }) => `self.serviceWorkerOption = { assets: ${JSON.stringify(assets)} };`
export const devSwPlugin = ({
swSrc,
swDest,
@ -32,12 +37,16 @@ export const devSwPlugin = ({
const name = id.startsWith('/') ? id.slice(1) : id
if (name === swDest) {
return swFullSrc
} else if (name === swEnvName) {
return swEnvNameResolved
}
return null
},
async load (id) {
if (id === swFullSrc) {
return readFile(swFullSrc, 'utf-8')
} else if (id === swEnvNameResolved) {
return getDevSwEnv()
}
return null
},
@ -79,6 +88,21 @@ export const devSwPlugin = ({
contents: await getSWMessagesAsText()
}))
}
}, {
name: 'sw-env',
setup (b) {
b.onResolve(
{ filter: new RegExp('^' + swEnvName + '$') },
args => ({
path: args.path,
namespace: 'sw-env'
}))
b.onLoad(
{ filter: /.*/, namespace: 'sw-env' },
() => ({
contents: getDevSwEnv()
}))
}
}]
})
const text = res.outputFiles[0].text
@ -126,6 +150,30 @@ export const buildSwPlugin = ({
configFile: false
}
},
generateBundle: {
order: 'post',
sequential: true,
async handler (_, bundle) {
const assets = Object.keys(bundle)
.filter(name => !/\.map$/.test(name))
.map(name => '/' + name)
config.plugins.push({
name: 'build-sw-env-plugin',
resolveId (id) {
if (id === swEnvName) {
return swEnvNameResolved
}
return null
},
load (id) {
if (id === swEnvNameResolved) {
return getProdSwEnv({ assets })
}
return null
}
})
}
},
closeBundle: {
order: 'post',
sequential: true,

View file

@ -0,0 +1 @@
Display counter for status action buttons when they are on the menu

View file

View file

@ -0,0 +1 @@
Added support for Akkoma and IceShrimp.NET backend

0
changelog.d/akkoma.skip Normal file
View file

View file

@ -0,0 +1,2 @@
Add arithmetic blend ISS function

View file

@ -0,0 +1 @@
Fix bookmark button alignment in the extra actions menu

1
changelog.d/csp.add Normal file
View file

@ -0,0 +1 @@
Compatibility with stricter CSP (Akkoma backend)

View file

View file

View file

@ -0,0 +1 @@
Cache assets and emojis with service worker

View file

@ -0,0 +1 @@
Unify show/hide content buttons

0
changelog.d/zoomlag.skip Normal file
View file

View file

@ -5,138 +5,16 @@
<meta name="viewport" content="width=device-width, initial-scale=1,user-scalable=no">
<link rel="preload" href="/static/config.json" as="fetch" crossorigin />
<link rel="preload" href="/api/pleroma/frontend_configurations" as="fetch" crossorigin />
<link rel="preload" href="/nodeinfo/2.0.json" as="fetch" crossorigin />
<link rel="preload" href="/nodeinfo/2.1.json" as="fetch" crossorigin />
<link rel="preload" href="/api/v1/instance" as="fetch" crossorigin />
<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 -->
<style id="splashscreen">
#splash {
--scale: 1;
width: 100vw;
height: 100vh;
display: grid;
grid-template-rows: auto;
grid-template-columns: auto;
align-content: center;
align-items: center;
justify-content: center;
justify-items: center;
flex-direction: column;
background: #0f161e;
font-family: sans-serif;
color: #b9b9ba;
position: absolute;
z-index: 9999;
font-size: calc(1vw + 1vh + 1vmin);
}
#splash-credit {
position: absolute;
font-size: 14px;
bottom: 16px;
right: 16px;
}
#splash-container {
align-items: center;
}
#mascot-container {
display: flex;
align-items: flex-end;
justify-content: center;
perspective: 60em;
perspective-origin: 0 -15em;
transform-style: preserve-3d;
}
#mascot {
width: calc(10em * var(--scale));
height: calc(10em * var(--scale));
object-fit: contain;
object-position: bottom;
transform: translateZ(-2em);
}
#throbber {
display: grid;
width: calc(5em * 0.5 * var(--scale));
height: calc(8em * 0.5 * var(--scale));
margin-left: 4.1em;
z-index: 2;
grid-template-rows: repeat(8, 1fr);
grid-template-columns: repeat(5, 1fr);
grid-template-areas: "P P . L L"
"P P . L L"
"P P . L L"
"P P . L L"
"P P . . ."
"P P . . ."
"P P . E E"
"P P . E E";
--logoChunkSize: calc(2em * 0.5 * var(--scale))
}
.chunk {
background-color: #e2b188;
box-shadow: 0.01em 0.01em 0.1em 0 #e2b188;
}
#chunk-P {
grid-area: P;
border-top-left-radius: calc(var(--logoChunkSize) / 2);
}
#chunk-L {
grid-area: L;
border-bottom-right-radius: calc(var(--logoChunkSize) / 2);
}
#chunk-E {
grid-area: E;
border-bottom-right-radius: calc(var(--logoChunkSize) / 2);
}
#status {
margin-top: 1em;
line-height: 2;
width: 100%;
text-align: center;
}
#statusError {
display: none;
margin-top: 1em;
font-size: calc(1vw + 1vh + 1vmin);
line-height: 2;
width: 100%;
text-align: center;
}
#statusStack {
display: none;
margin-top: 1em;
font-size: calc((1vw + 1vh + 1vmin) / 2.5);
width: calc(100vw - 5em);
padding: 1em;
text-overflow: ellipsis;
overflow-x: hidden;
text-align: left;
line-height: 2;
}
@media (prefers-reduced-motion) {
#throbber {
animation: none !important;
}
}
</style>
<style id="pleroma-eager-styles" type="text/css"></style>
<style id="pleroma-lazy-styles" type="text/css"></style>
<link rel="stylesheet" id="splashscreen" href="/static/splash.css" />
<link rel="stylesheet" id="custom-styles-holder" type="text/css" href="/static/empty.css" />
<!--server-generated-meta-->
</head>
<body style="margin: 0; padding: 0">
<body>
<noscript>To use Pleroma, please enable JavaScript.</noscript>
<div id="splash">
<!-- we are hiding entire graphic so no point showing credit -->

View file

@ -23,7 +23,7 @@
"@fortawesome/free-regular-svg-icons": "6.7.2",
"@fortawesome/free-solid-svg-icons": "6.7.2",
"@fortawesome/vue-fontawesome": "3.0.8",
"@kazvmoe-infra/pinch-zoom-element": "1.2.0",
"@kazvmoe-infra/pinch-zoom-element": "1.3.0",
"@kazvmoe-infra/unicode-emoji-json": "0.4.0",
"@ruffle-rs/ruffle": "0.1.0-nightly.2025.1.13",
"@vuelidate/core": "2.0.3",
@ -47,7 +47,7 @@
"url": "0.11.4",
"utf8": "3.0.0",
"uuid": "11.1.0",
"vue": "3.5.13",
"vue": "3.5.17",
"vue-i18n": "11",
"vue-router": "4.5.1",
"vue-virtual-scroller": "^2.0.0-beta.7",
@ -66,7 +66,7 @@
"@vitest/ui": "^3.0.7",
"@vue/babel-helper-vue-jsx-merge-props": "1.4.0",
"@vue/babel-plugin-jsx": "1.4.0",
"@vue/compiler-sfc": "3.5.13",
"@vue/compiler-sfc": "3.5.17",
"@vue/test-utils": "2.4.6",
"autoprefixer": "10.4.21",
"babel-plugin-lodash": "3.3.4",
@ -90,17 +90,17 @@
"http-proxy-middleware": "3.0.5",
"iso-639-1": "3.1.5",
"lodash": "4.17.21",
"msw": "2.7.6",
"msw": "2.10.2",
"nightwatch": "3.12.1",
"playwright": "1.52.0",
"postcss": "8.5.3",
"postcss-html": "^1.5.0",
"postcss-scss": "^4.0.6",
"sass": "1.87.0",
"sass": "1.89.2",
"selenium-server": "3.141.59",
"semver": "7.7.1",
"semver": "7.7.2",
"serve-static": "2.2.0",
"shelljs": "0.9.2",
"shelljs": "0.10.0",
"sinon": "20.0.0",
"sinon-chai": "4.0.0",
"stylelint": "16.19.1",

126
public/static/splash.css Normal file
View file

@ -0,0 +1,126 @@
body {
margin: 0;
padding: 0;
}
#splash {
--scale: 1;
width: 100vw;
height: 100vh;
display: grid;
grid-template-rows: auto;
grid-template-columns: auto;
align-content: center;
align-items: center;
justify-content: center;
justify-items: center;
flex-direction: column;
background: #0f161e;
font-family: sans-serif;
color: #b9b9ba;
position: absolute;
z-index: 9999;
font-size: calc(1vw + 1vh + 1vmin);
}
#splash-credit {
position: absolute;
font-size: 14px;
bottom: 16px;
right: 16px;
}
#splash-container {
align-items: center;
}
#mascot-container {
display: flex;
align-items: flex-end;
justify-content: center;
perspective: 60em;
perspective-origin: 0 -15em;
transform-style: preserve-3d;
}
#mascot {
width: calc(10em * var(--scale));
height: calc(10em * var(--scale));
object-fit: contain;
object-position: bottom;
transform: translateZ(-2em);
}
#throbber {
display: grid;
width: calc(5em * 0.5 * var(--scale));
height: calc(8em * 0.5 * var(--scale));
margin-left: 4.1em;
z-index: 2;
grid-template-rows: repeat(8, 1fr);
grid-template-columns: repeat(5, 1fr);
grid-template-areas: "P P . L L"
"P P . L L"
"P P . L L"
"P P . L L"
"P P . . ."
"P P . . ."
"P P . E E"
"P P . E E";
--logoChunkSize: calc(2em * 0.5 * var(--scale))
}
.chunk {
background-color: #e2b188;
box-shadow: 0.01em 0.01em 0.1em 0 #e2b188;
}
#chunk-P {
grid-area: P;
border-top-left-radius: calc(var(--logoChunkSize) / 2);
}
#chunk-L {
grid-area: L;
border-bottom-right-radius: calc(var(--logoChunkSize) / 2);
}
#chunk-E {
grid-area: E;
border-bottom-right-radius: calc(var(--logoChunkSize) / 2);
}
#status {
margin-top: 1em;
line-height: 2;
width: 100%;
text-align: center;
}
#statusError {
display: none;
margin-top: 1em;
font-size: calc(1vw + 1vh + 1vmin);
line-height: 2;
width: 100%;
text-align: center;
}
#statusStack {
display: none;
margin-top: 1em;
font-size: calc((1vw + 1vh + 1vmin) / 2.5);
width: calc(100vw - 5em);
padding: 1em;
text-overflow: ellipsis;
overflow-x: hidden;
text-align: left;
line-height: 2;
}
@media (prefers-reduced-motion) {
#throbber {
animation: none !important;
}
}

View file

@ -14,6 +14,7 @@ import EditStatusModal from './components/edit_status_modal/edit_status_modal.vu
import PostStatusModal from './components/post_status_modal/post_status_modal.vue'
import StatusHistoryModal from './components/status_history_modal/status_history_modal.vue'
import GlobalNoticeList from './components/global_notice_list/global_notice_list.vue'
import { getOrCreateServiceWorker } from './services/sw/sw'
import { windowWidth, windowHeight } from './services/window_utils/window_utils'
import { mapGetters } from 'vuex'
import { defineAsyncComponent } from 'vue'
@ -77,6 +78,7 @@ export default {
this.setThemeBodyClass()
this.removeSplash()
}
getOrCreateServiceWorker()
},
unmounted () {
window.removeEventListener('resize', this.updateMobileState)

View file

@ -2,6 +2,9 @@
/* stylelint-disable no-descending-specificity */
@use "panel";
@import '@fortawesome/fontawesome-svg-core/styles.css';
@import '@kazvmoe-infra/pinch-zoom-element/dist/pinch-zoom.css';
:root {
--status-margin: 0.75em;
--post-line-height: 1.4;
@ -30,6 +33,7 @@ body {
font-family: sans-serif;
font-family: var(--font);
margin: 0;
padding: 0;
color: var(--text);
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;

View file

@ -6,6 +6,8 @@ import VueVirtualScroller from 'vue-virtual-scroller'
import 'vue-virtual-scroller/dist/vue-virtual-scroller.css'
import { FontAwesomeIcon, FontAwesomeLayers } from '@fortawesome/vue-fontawesome'
import { config } from '@fortawesome/fontawesome-svg-core';
config.autoAddCss = false
import App from '../App.vue'
import routes from './routes'
@ -21,6 +23,7 @@ import { useOAuthStore } from 'src/stores/oauth'
import { useI18nStore } from 'src/stores/i18n'
import { useInterfaceStore } from 'src/stores/interface'
import { useAnnouncementsStore } from 'src/stores/announcements'
import { useAuthFlowStore } from 'src/stores/auth_flow'
let staticInitialResults = null
@ -63,10 +66,11 @@ const getInstanceConfig = async ({ store }) => {
const textlimit = data.max_toot_chars
const vapidPublicKey = data.pleroma.vapid_public_key
store.dispatch('setInstanceOption', { name: 'pleromaExtensionsAvailable', value: data.pleroma })
store.dispatch('setInstanceOption', { name: 'textlimit', value: textlimit })
store.dispatch('setInstanceOption', { name: 'accountApprovalRequired', value: data.approval_required })
store.dispatch('setInstanceOption', { name: 'birthdayRequired', value: !!data.pleroma.metadata.birthday_required })
store.dispatch('setInstanceOption', { name: 'birthdayMinAge', value: data.pleroma.metadata.birthday_min_age || 0 })
store.dispatch('setInstanceOption', { name: 'birthdayRequired', value: !!data.pleroma?.metadata.birthday_required })
store.dispatch('setInstanceOption', { name: 'birthdayMinAge', value: data.pleroma?.metadata.birthday_min_age || 0 })
if (vapidPublicKey) {
store.dispatch('setInstanceOption', { name: 'vapidPublicKey', value: vapidPublicKey })
@ -78,6 +82,8 @@ const getInstanceConfig = async ({ store }) => {
console.error('Could not load instance config, potentially fatal')
console.error(error)
}
// We should check for scrobbles support here but it requires userId
// so instead we check for it where it's fetched (statuses.js)
}
const getBackendProvidedConfig = async () => {
@ -153,7 +159,7 @@ const setSettings = async ({ apiConfig, staticConfig, store }) => {
: config.logoMargin
})
copyInstanceOption('logoLeft')
store.commit('authFlow/setInitialStrategy', config.loginMethod)
useAuthFlowStore().setInitialStrategy(config.loginMethod)
copyInstanceOption('redirectRootNoLogin')
copyInstanceOption('redirectRootLogin')
@ -242,7 +248,8 @@ const resolveStaffAccounts = ({ store, accounts }) => {
const getNodeInfo = async ({ store }) => {
try {
const res = await preloadFetch('/nodeinfo/2.1.json')
let res = await preloadFetch('/nodeinfo/2.1.json')
if (!res.ok) res = await preloadFetch('/nodeinfo/2.0.json')
if (res.ok) {
const data = await res.json()
const metadata = data.metadata
@ -254,7 +261,12 @@ const getNodeInfo = async ({ store }) => {
store.dispatch('setInstanceOption', { name: 'safeDM', value: features.includes('safe_dm_mentions') })
store.dispatch('setInstanceOption', { name: 'shoutAvailable', value: features.includes('chat') })
store.dispatch('setInstanceOption', { name: 'pleromaChatMessagesAvailable', value: features.includes('pleroma_chat_messages') })
store.dispatch('setInstanceOption', { name: 'pleromaCustomEmojiReactionsAvailable', value: features.includes('pleroma_custom_emoji_reactions') })
store.dispatch('setInstanceOption', {
name: 'pleromaCustomEmojiReactionsAvailable',
value:
features.includes('pleroma_custom_emoji_reactions') ||
features.includes('custom_emoji_reactions')
})
store.dispatch('setInstanceOption', { name: 'pleromaBookmarkFoldersAvailable', value: features.includes('pleroma:bookmark_folders') })
store.dispatch('setInstanceOption', { name: 'gopherAvailable', value: features.includes('gopher') })
store.dispatch('setInstanceOption', { name: 'pollsAvailable', value: features.includes('polls') })
@ -264,6 +276,7 @@ const getNodeInfo = async ({ store }) => {
store.dispatch('setInstanceOption', { name: 'quotingAvailable', value: features.includes('quote_posting') })
store.dispatch('setInstanceOption', { name: 'groupActorAvailable', value: features.includes('pleroma:group_actors') })
store.dispatch('setInstanceOption', { name: 'blockExpiration', value: features.includes('pleroma:block_expiration') })
store.dispatch('setInstanceOption', { name: 'localBubbleInstances', value: metadata.localBubbleInstances ?? [] })
const uploadLimits = metadata.uploadLimits
store.dispatch('setInstanceOption', { name: 'uploadlimit', value: parseInt(uploadLimits.general) })
@ -282,7 +295,6 @@ const getNodeInfo = async ({ store }) => {
const software = data.software
store.dispatch('setInstanceOption', { name: 'backendVersion', value: software.version })
store.dispatch('setInstanceOption', { name: 'backendRepository', value: software.repository })
store.dispatch('setInstanceOption', { name: 'pleromaBackend', value: software.name === 'pleroma' })
const priv = metadata.private
store.dispatch('setInstanceOption', { name: 'private', value: priv })

View file

@ -1,4 +1,5 @@
import PublicTimeline from 'components/public_timeline/public_timeline.vue'
import BubbleTimeline from 'components/bubble_timeline/bubble_timeline.vue'
import PublicAndExternalTimeline from 'components/public_and_external_timeline/public_and_external_timeline.vue'
import FriendsTimeline from 'components/friends_timeline/friends_timeline.vue'
import TagTimeline from 'components/tag_timeline/tag_timeline.vue'
@ -54,6 +55,7 @@ export default (store) => {
{ name: 'friends', path: '/main/friends', component: FriendsTimeline, beforeEnter: validateAuthenticatedRoute },
{ name: 'tag-timeline', path: '/tag/:tag', component: TagTimeline },
{ name: 'bookmarks', path: '/bookmarks', component: BookmarkTimeline },
{ name: 'bubble', path: '/bubble', component: BubbleTimeline },
{ name: 'conversation', path: '/notice/:id', component: ConversationPage, meta: { dontScroll: true } },
{ name: 'quotes', path: '/notice/:id/quotes', component: QuotesTimeline },
{

View file

@ -2,7 +2,8 @@ import { h, resolveComponent } from 'vue'
import LoginForm from '../login_form/login_form.vue'
import MFARecoveryForm from '../mfa_form/recovery_form.vue'
import MFATOTPForm from '../mfa_form/totp_form.vue'
import { mapGetters } from 'vuex'
import { mapState } from 'pinia'
import { useAuthFlowStore } from 'src/stores/auth_flow'
const AuthForm = {
name: 'AuthForm',
@ -15,7 +16,7 @@ const AuthForm = {
if (this.requiredRecovery) { return 'MFARecoveryForm' }
return 'LoginForm'
},
...mapGetters('authFlow', ['requiredTOTP', 'requiredRecovery'])
...mapState(useAuthFlowStore, ['requiredTOTP', 'requiredRecovery'])
},
components: {
MFARecoveryForm,

View file

@ -0,0 +1,18 @@
import Timeline from '../timeline/timeline.vue'
const BubbleTimeline = {
components: {
Timeline
},
computed: {
timeline () { return this.$store.state.statuses.timelines.bubble }
},
created () {
this.$store.dispatch('startFetchingTimeline', { timeline: 'bubble' })
},
unmounted () {
this.$store.dispatch('stopFetchingTimeline', 'bubble')
}
}
export default BubbleTimeline

View file

@ -0,0 +1,9 @@
<template>
<Timeline
:title="$t('nav.bubble')"
:timeline="timeline"
:timeline-name="'bubble'"
/>
</template>
<script src="./bubble_timeline.js"></script>

View file

@ -26,7 +26,7 @@
class="textColor unstyled"
:class="{ disabled: !present || disabled }"
type="text"
:value="modelValue || fallback"
:value="modelValue ?? fallback"
:disabled="!present || disabled"
@input="updateValue($event.target.value)"
>

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', 90)
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>
<div
:id="'component-preview-' + randomSeed"
class="ComponentPreview"
: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
v-show="shadowControl"
role="heading"
@ -74,7 +69,6 @@
<div
class="preview-block"
:class="previewClass"
:style="style"
>
{{ $t('settings.style.themes3.editor.test_string') }}
</div>
@ -116,203 +110,5 @@
</div>
</template>
<script>
import Checkbox from 'src/components/checkbox/checkbox.vue'
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>
<script src="./component_preview.js" />
<style src="./component_preview.scss" lang="scss" />

View file

@ -1,7 +1,8 @@
import { mapState, mapGetters, mapActions, mapMutations } from 'vuex'
import { mapStores } from 'pinia'
import { mapState } from 'vuex'
import { mapStores, mapActions, mapState as mapPiniaState } from 'pinia'
import oauthApi from '../../services/new_api/oauth.js'
import { useOAuthStore } from 'src/stores/oauth.js'
import { useAuthFlowStore } from 'src/stores/auth_flow.js'
import { library } from '@fortawesome/fontawesome-svg-core'
import {
faTimes
@ -25,13 +26,10 @@ const LoginForm = {
instance: state => state.instance,
loggingIn: state => state.users.loggingIn,
}),
...mapGetters(
'authFlow', ['requiredPassword', 'requiredToken', 'requiredMFA']
)
...mapPiniaState(useAuthFlowStore, ['requiredPassword', 'requiredToken', 'requiredMFA'])
},
methods: {
...mapMutations('authFlow', ['requireMFA']),
...mapActions({ login: 'authFlow/login' }),
...mapActions(useAuthFlowStore, ['requireMFA', 'login']),
submit () {
this.isTokenAuth ? this.submitToken() : this.submitPassword()
},

View file

@ -1,7 +1,8 @@
import mfaApi from '../../services/new_api/mfa.js'
import { mapState, mapGetters, mapActions, mapMutations } from 'vuex'
import { mapStores } from 'pinia'
import { mapState } from 'vuex'
import { mapStores, mapActions, mapState as mapPiniaState } from 'pinia'
import { useOAuthStore } from 'src/stores/oauth.js'
import { useAuthFlowStore } from 'src/stores/auth_flow.js'
import { library } from '@fortawesome/fontawesome-svg-core'
import {
faTimes
@ -17,8 +18,8 @@ export default {
error: false
}),
computed: {
...mapGetters({
authSettings: 'authFlow/settings'
...mapPiniaState(useAuthFlowStore, {
authSettings: store => store.settings
}),
...mapStores(useOAuthStore),
...mapState({
@ -26,8 +27,7 @@ export default {
})
},
methods: {
...mapMutations('authFlow', ['requireTOTP', 'abortMFA']),
...mapActions({ login: 'authFlow/login' }),
...mapActions(useAuthFlowStore, ['requireTOTP', 'abortMFA', 'login']),
clearError () { this.error = false },
focusOnCodeInput () {

View file

@ -1,7 +1,8 @@
import mfaApi from '../../services/new_api/mfa.js'
import { mapState, mapGetters, mapActions, mapMutations } from 'vuex'
import { mapStores } from 'pinia'
import { mapState } from 'vuex'
import { mapStores, mapActions, mapState as mapPiniaState } from 'pinia'
import { useOAuthStore } from 'src/stores/oauth.js'
import { useAuthFlowStore } from 'src/stores/auth_flow.js'
import { library } from '@fortawesome/fontawesome-svg-core'
import {
faTimes
@ -17,8 +18,8 @@ export default {
error: false
}),
computed: {
...mapGetters({
authSettings: 'authFlow/settings'
...mapPiniaState(useAuthFlowStore, {
authSettings: store => store.settings
}),
...mapStores(useOAuthStore),
...mapState({
@ -26,8 +27,7 @@ export default {
})
},
methods: {
...mapMutations('authFlow', ['requireRecovery', 'abortMFA']),
...mapActions({ login: 'authFlow/login' }),
...mapActions(useAuthFlowStore, ['requireRecovery', 'abortMFA', 'login']),
clearError () { this.error = false },
focusOnCodeInput () {

View file

@ -15,6 +15,7 @@ import { library } from '@fortawesome/fontawesome-svg-core'
import {
faUsers,
faGlobe,
faCity,
faBookmark,
faEnvelope,
faChevronDown,
@ -31,6 +32,7 @@ import {
library.add(
faUsers,
faGlobe,
faCity,
faBookmark,
faEnvelope,
faChevronDown,
@ -108,12 +110,15 @@ const NavPanel = {
privateMode: state => state.instance.private,
federating: state => state.instance.federating,
pleromaChatMessagesAvailable: state => state.instance.pleromaChatMessagesAvailable,
bookmarkFolders: state => state.instance.pleromaBookmarkFoldersAvailable
bookmarkFolders: state => state.instance.pleromaBookmarkFoldersAvailable,
bubbleTimeline: state => state.instance.localBubbleInstances.length > 0
}),
timelinesItems () {
return filterNavigation(
Object
.entries({ ...TIMELINES })
// do not show in timeliens list since it's in a better place now
.filter(([key]) => key !== 'bookmarks')
.map(([k, v]) => ({ ...v, name: k })),
{
hasChats: this.pleromaChatMessagesAvailable,
@ -121,6 +126,7 @@ const NavPanel = {
isFederating: this.federating,
isPrivate: this.privateMode,
currentUser: this.currentUser,
supportsBubbleTimeline: this.bubbleTimeline,
supportsBookmarkFolders: this.bookmarkFolders
}
)
@ -136,6 +142,7 @@ const NavPanel = {
isFederating: this.federating,
isPrivate: this.privateMode,
currentUser: this.currentUser,
supportsBubbleTimeline: this.bubbleTimeline,
supportsBookmarkFolders: this.bookmarkFolders
}
)

View file

@ -1,4 +1,12 @@
export const filterNavigation = (list = [], { hasChats, hasAnnouncements, isFederating, isPrivate, currentUser, supportsBookmarkFolders }) => {
export const filterNavigation = (list = [], {
hasChats,
hasAnnouncements,
isFederating,
isPrivate,
currentUser,
supportsBookmarkFolders,
supportsBubbleTimeline
}) => {
return list.filter(({ criteria, anon, anonRoute }) => {
const set = new Set(criteria || [])
if (!isFederating && set.has('federating')) return false
@ -7,6 +15,8 @@ export const filterNavigation = (list = [], { hasChats, hasAnnouncements, isFede
if ((!currentUser || !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')) return false
if (!supportsBookmarkFolders && set.has('supportsBookmarkFolders')) return false
if (supportsBookmarkFolders && set.has('!supportsBookmarkFolders')) return false
return true
})
@ -19,11 +29,11 @@ export const getListEntries = store => store.allLists.map(list => ({
iconLetter: list.title[0]
}))
export const getBookmarkFolderEntries = store => store.allFolders.map(folder => ({
export const getBookmarkFolderEntries = store => store.allFolders ? store.allFolders.map(folder => ({
name: 'bookmark-folder-' + folder.id,
routeObject: { name: 'bookmark-folder', params: { id: folder.id } },
labelRaw: folder.name,
iconEmoji: folder.emoji,
iconEmojiUrl: folder.emoji_url,
iconLetter: folder.name[0]
}))
})) : []

View file

@ -27,6 +27,13 @@ export const TIMELINES = {
label: 'nav.public_tl',
criteria: ['!private']
},
bubble: {
route: 'bubble',
anon: true,
icon: 'city',
label: 'nav.bubble',
criteria: ['!private', 'federating', 'supportsBubbleTimeline']
},
twkn: {
route: 'public-external-timeline',
anon: true,
@ -34,11 +41,11 @@ export const TIMELINES = {
label: 'nav.twkn',
criteria: ['!private', 'federating']
},
// bookmarks are still technically a timeline so we should show it in the dropdown
bookmarks: {
route: 'bookmarks',
icon: 'bookmark',
label: 'nav.bookmarks',
criteria: ['!supportsBookmarkFolders']
},
favorites: {
routeObject: { name: 'user-profile', query: { tab: 'favorites' } },
@ -53,6 +60,15 @@ export const TIMELINES = {
}
export const ROOT_ITEMS = {
bookmarks: {
route: 'bookmarks',
icon: 'bookmark',
label: 'nav.bookmarks',
// shows bookmarks entry in a better suited location
// hides it when bookmark folders are supported since
// we show custom component instead of it
criteria: ['!supportsBookmarkFolders']
},
interactions: {
route: 'interactions',
icon: 'bell',

View file

@ -9,6 +9,7 @@ import { library } from '@fortawesome/fontawesome-svg-core'
import {
faUsers,
faGlobe,
faCity,
faBookmark,
faEnvelope,
faComments,
@ -25,6 +26,7 @@ import { useServerSideStorageStore } from 'src/stores/serverSideStorage'
library.add(
faUsers,
faGlobe,
faCity,
faBookmark,
faEnvelope,
faComments,
@ -65,7 +67,8 @@ const NavPanel = {
followRequestCount: state => state.api.followRequests.length,
privateMode: state => state.instance.private,
federating: state => state.instance.federating,
pleromaChatMessagesAvailable: state => state.instance.pleromaChatMessagesAvailable
pleromaChatMessagesAvailable: state => state.instance.pleromaChatMessagesAvailable,
bubbleTimeline: state => state.instance.localBubbleInstances.length > 0
}),
pinnedList () {
if (!this.currentUser) {
@ -79,7 +82,9 @@ const NavPanel = {
hasAnnouncements: this.supportsAnnouncements,
isFederating: this.federating,
isPrivate: this.privateMode,
currentUser: this.currentUser
currentUser: this.currentUser,
supportsBubbleTimeline: this.bubbleTimeline,
supportsBookmarkFolders: this.bookmarks
})
}
return filterNavigation(
@ -98,6 +103,8 @@ const NavPanel = {
{
hasChats: this.pleromaChatMessagesAvailable,
hasAnnouncements: this.supportsAnnouncements,
supportsBubbleTimeline: this.bubbleTimeline,
supportsBookmarkFolders: this.bookmarks,
isFederating: this.federating,
isPrivate: this.privateMode,
currentUser: this.currentUser

View file

@ -60,11 +60,14 @@
}
.extra-button {
border-left: 1px solid var(--icon);
border-left: 1px solid;
border-image-source: linear-gradient(to bottom, transparent 0%, var(--icon) var(--__horizontal-gap) calc(100% - var(--__horizontal-gap)), transparent 100%);
border-image-slice: 1;
padding-left: calc(var(--__horizontal-gap) - 1px);
border-right: var(--__horizontal-gap) solid transparent;
border-top: var(--__horizontal-gap) solid transparent;
border-bottom: var(--__horizontal-gap) solid transparent;
padding-right: var(--__horizontal-gap);
padding-top: var(--__horizontal-gap);
padding-bottom: var(--__horizontal-gap);
max-width: fit-content;
}
.main-button {

View file

@ -12,10 +12,10 @@ import { newImporter } from 'src/services/export_import/export_import.js'
import { convertTheme2To3 } from 'src/services/theme_data/theme2_to_theme3.js'
import { init } from 'src/services/theme_data/theme_data_3.service.js'
import {
getCssRules,
getScopedVersion
getCssRules
} from 'src/services/theme_data/css_utils.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 ProfileSettingIndicator from '../helpers/profile_setting_indicator.vue'
@ -155,19 +155,23 @@ const AppearanceTab = {
}))
})
this.previewTheme('stock', 'v3')
if (window.IntersectionObserver) {
this.intersectionObserver = new IntersectionObserver((entries, observer) => {
entries.forEach(({ target, isIntersecting }) => {
if (!isIntersecting) return
const theme = this.availableStyles.find(x => x.key === target.dataset.themeKey)
this.$nextTick(() => {
if (theme) theme.ready = true
if (theme) this.previewTheme(theme.key, theme.version, theme.data)
})
observer.unobserve(target)
})
}, {
root: this.$refs.themeList
})
} else {
this.availableStyles.forEach(theme => this.previewTheme(theme.key, theme.version, theme.data))
}
},
updated () {
@ -391,7 +395,6 @@ const AppearanceTab = {
inputRuleset: [...input, paletteRule].filter(x => x),
ultimateBackgroundColor: '#000000',
liteMode: true,
debug: true,
onlyNormalState: true
})
}
@ -400,7 +403,6 @@ const AppearanceTab = {
inputRuleset: [],
ultimateBackgroundColor: '#000000',
liteMode: true,
debug: true,
onlyNormalState: true
})
}
@ -409,10 +411,15 @@ const AppearanceTab = {
this.compilationCache[key] = theme3
}
return getScopedVersion(
getCssRules(theme3.eager),
'#theme-preview-' + key
).join('\n')
const sheet = createStyleSheet('appearance-tab-previews', 90)
sheet.addRule([
'#theme-preview-', key, ' {\n',
getCssRules(theme3.eager).join('\n'),
'\n}'
].join(''))
sheet.ready = true
adoptStyleSheets()
}
}
}

View file

@ -16,14 +16,6 @@
:disabled="switchInProgress"
@click="resetTheming"
>
<!-- eslint-disable vue/no-v-text-v-html-on-component -->
<!-- eslint-disable vue/no-v-html -->
<component
:is="'style'"
v-html="previewTheme('stock', 'v3')"
/>
<!-- eslint-enable vue/no-v-html -->
<!-- eslint-enable vue/no-v-text-v-html-on-component -->
<preview id="theme-preview-stock" />
<h4 class="theme-name">
{{ $t('settings.style.stock_theme_used') }}
@ -61,16 +53,6 @@
:disabled="switchInProgress"
@click="style.version === 'v2' ? setTheme(style.key) : setStyle(style.key)"
>
<!-- eslint-disable vue/no-v-text-v-html-on-component -->
<!-- eslint-disable vue/no-v-html -->
<div v-if="style.ready || noIntersectionObserver">
<component
:is="'style'"
v-html="previewTheme(style.key, style.version, style.data)"
/>
</div>
<!-- eslint-enable vue/no-v-html -->
<!-- eslint-enable vue/no-v-text-v-html-on-component -->
<preview :id="'theme-preview-' + style.key" />
<h4 class="theme-name">
{{ style.name }}

View file

@ -8,6 +8,7 @@ import InterfaceLanguageSwitcher from 'src/components/interface_language_switche
import SharedComputedObject from '../helpers/shared_computed_object.js'
import ProfileSettingIndicator from '../helpers/profile_setting_indicator.vue'
import { clearCache, cacheKey, emojiCacheKey } from 'src/services/sw/sw.js'
import { library } from '@fortawesome/fontawesome-svg-core'
import {
faGlobe
@ -98,6 +99,21 @@ const GeneralTab = {
methods: {
changeDefaultScope (value) {
this.$store.dispatch('setProfileOption', { name: 'defaultScope', value })
},
clearCache (key) {
clearCache(key)
.then(() => {
this.$store.dispatch('settingsSaved', { success: true })
})
.catch(error => {
this.$store.dispatch('settingsSaved', { error })
})
},
clearAssetCache () {
this.clearCache(cacheKey)
},
clearEmojiCache () {
this.clearCache(emojiCacheKey)
}
}
}

View file

@ -509,6 +509,29 @@
</li>
</ul>
</div>
<div
class="setting-item"
>
<h2>{{ $t('settings.cache') }}</h2>
<ul class="setting-list">
<li>
<button
class="btn button-default"
@click="clearAssetCache"
>
{{ $t('settings.clear_asset_cache') }}
</button>
</li>
<li>
<button
class="btn button-default"
@click="clearEmojiCache"
>
{{ $t('settings.clear_emoji_cache') }}
</button>
</li>
</ul>
</div>
</div>
</template>

View file

@ -41,8 +41,8 @@ const SecurityTab = {
user () {
return this.$store.state.users.currentUser
},
pleromaBackend () {
return this.$store.state.instance.pleromaBackend
pleromaExtensionsAvailable () {
return this.$store.state.instance.pleromaExtensionsAvailable
},
oauthTokens () {
return useOAuthTokensStore().tokens.map(oauthToken => {

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 { 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 { createStyleSheet, adoptStyleSheets } from 'src/services/style_setter/style_setter.js'
import { init, findColor } from 'src/services/theme_data/theme_data_3.service.js'
import {
getCssRules,
getScopedVersion
} from 'src/services/theme_data/css_utils.js'
import { getCssRules } from 'src/services/theme_data/css_utils.js'
import { serialize } from 'src/services/theme_data/iss_serializer.js'
import { deserializeShadow, deserialize } from 'src/services/theme_data/iss_deserializer.js'
import {
@ -372,6 +370,9 @@ export default {
const path = getPath(component, directive)
usedRule = get(real, path) // get real
if (usedRule === '') {
return usedRule
}
if (!usedRule) {
usedRule = get(fallback, path)
}
@ -379,7 +380,7 @@ export default {
return postProcess(usedRule)
},
set (value) {
if (value) {
if (value != null) {
set(allEditedRules.value, getPath(component, directive), value)
} else {
unset(allEditedRules.value, getPath(component, directive))
@ -667,7 +668,7 @@ export default {
})
exports.clearStyle = () => {
onImport(interfaceStore().styleDataUsed)
onImport(interfaceStore.styleDataUsed)
}
exports.exportStyle = () => {
@ -685,19 +686,26 @@ export default {
const overallPreviewRules = ref([])
exports.overallPreviewRules = overallPreviewRules
const overallPreviewCssRules = ref([])
watchEffect(throttle(() => {
watch([overallPreviewRules], () => {
let css = null
try {
overallPreviewCssRules.value = getScopedVersion(
getCssRules(overallPreviewRules.value),
'#edited-style-preview'
).join('\n')
css = getCssRules(overallPreviewRules.value).map(r => r.replace('html', '&'))
} catch (e) {
console.error(e)
return
}
}, 500))
exports.overallPreviewCssRules = overallPreviewCssRules
const sheet = createStyleSheet('style-tab-overall-preview', 90)
sheet.clear()
sheet.addRule([
'#edited-style-preview {\n',
css.join('\n'),
'\n}'
].join(''))
sheet.ready = true
adoptStyleSheets()
})
const updateOverallPreview = throttle(() => {
try {
@ -721,12 +729,12 @@ export default {
console.error('Could not compile preview theme', e)
return null
}
}, 5000)
}, 1000)
//
// Apart from "hover" we can't really show how component looks like in
// certain states, so we have to fake them.
const simulatePseudoSelectors = (css, prefix) => css
.replace(prefix, '.component-preview .preview-block')
.replace(prefix, '.preview-block')
.replace(':active', '.preview-active')
.replace(':hover', '.preview-hover')
.replace(':active', '.preview-active')

View file

@ -6,14 +6,6 @@
<div class="setting-item heading">
<h2> {{ $t('settings.style.themes3.editor.title') }} </h2>
<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" />
<teleport
v-if="isActive"
@ -155,12 +147,6 @@
</ul>
</div>
<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
class="component-preview"
:show-text="componentHas('Text')"

View file

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

View file

@ -123,12 +123,6 @@
</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" />
<div>

View file

@ -24,6 +24,7 @@ import {
faLock,
faLockOpen,
faGlobe,
faIgloo,
faTimes,
faRetweet,
faReply,
@ -43,6 +44,7 @@ import {
library.add(
faEnvelope,
faGlobe,
faIgloo,
faLock,
faLockOpen,
faTimes,
@ -484,6 +486,8 @@ const Status = {
return 'lock-open'
case 'direct':
return 'envelope'
case 'local':
return 'igloo'
default:
return 'globe'
}

View file

@ -102,4 +102,20 @@
}
}
}
&.-extra {
.action-counter {
justify-self: end;
margin-right: 1em;
}
.chevron-icon {
justify-self: end;
}
.extra-button {
justify-self: end;
justify-content: end;
}
}
}

View file

@ -60,7 +60,7 @@
/>
</component>
<span
v-if="!extra && button.counter?.(funcArg) > 0"
v-if="button.counter?.(funcArg) > 0"
class="action-counter"
>
{{ button.counter?.(funcArg) }}

View file

@ -72,6 +72,23 @@ const StatusContent = {
hideTallStatus () {
return this.mightHideBecauseTall && !this.showingTall
},
shouldShowToggle () {
return this.mightHideBecauseSubject || this.mightHideBecauseTall
},
toggleButtonClasses () {
return {
'cw-status-hider': !this.showingMore && this.mightHideBecauseSubject,
'tall-status-hider': !this.showingMore && this.mightHideBecauseTall,
'status-unhider': this.showingMore,
}
},
toggleText () {
if (this.showingMore) {
return this.mightHideBecauseSubject ? this.$t('status.hide_content') : this.$t('general.show_less')
} else {
return this.mightHideBecauseSubject ? this.$t('status.show_content') : this.$t('general.show_more')
}
},
showingMore () {
return (this.mightHideBecauseTall && this.showingTall) || (this.mightHideBecauseSubject && this.expandingSubject)
},

View file

@ -62,7 +62,6 @@
&.-tall-status {
position: relative;
height: 16em;
overflow: hidden;
z-index: 1;
.media-body {
@ -82,6 +81,10 @@
mask-composite: exclude;
}
}
&.-expanded {
overflow: visible;
}
}
& .tall-status-hider,
@ -95,6 +98,13 @@
text-align: center;
}
.status-unhider {
margin-top: auto;
position: sticky;
bottom: 0;
padding-bottom: 1em;
}
.tall-status-hider {
position: absolute;
height: 5em;
@ -118,6 +128,10 @@
}
}
.toggle-button {
padding: 0.5em;
}
&.-compact {
align-items: start;
flex-direction: row;
@ -166,11 +180,11 @@
line-height: inherit;
margin: 0;
border: none;
display: inline-block;
}
.text-wrapper {
display: inline-block;
width: 100%;
}
}
}

View file

@ -31,17 +31,9 @@
</button>
</div>
<div
:class="{'-tall-status': hideTallStatus}"
class="text-wrapper"
:class="{'-tall-status': hideTallStatus, '-expanded': showingMore}"
>
<button
v-show="hideTallStatus"
class="button-unstyled -link tall-status-hider"
:class="{ '-focused': focused }"
@click.prevent="toggleShowMore"
>
{{ $t("general.show_more") }}
</button>
<RichContent
v-if="!hideSubjectStatus && !(singleLine && status.summary_raw_html)"
:class="{ '-single-line': singleLine }"
@ -54,13 +46,18 @@
:attentions="status.attentions"
@parse-ready="onParseReady"
/>
<div
v-show="shouldShowToggle"
:class="toggleButtonClasses"
>
<button
v-show="hideSubjectStatus"
class="button-unstyled -link cw-status-hider"
class="btn button-default toggle-button"
:class="{ '-focused': focused }"
:aria-expanded="showingMore"
@click.prevent="toggleShowMore"
>
{{ $t("status.show_content") }}
{{ toggleText }}
<template v-if="!showingMore">
<FAIcon
v-if="attachmentTypes.includes('image')"
icon="image"
@ -85,14 +82,9 @@
v-if="status.card"
icon="link"
/>
</template>
</button>
<button
v-show="showingMore && !fullContent"
class="button-unstyled -link status-unhider"
@click.prevent="toggleShowMore"
>
{{ tallStatus ? $t("general.show_less") : $t("status.hide_content") }}
</button>
</div>
</div>
</div>
<slot v-if="!hideSubjectStatus" />

View file

@ -24,7 +24,8 @@ export const timelineNames = (supportsBookmarkFolders) => {
dms: 'nav.dms',
'public-timeline': 'nav.public_tl',
'public-external-timeline': 'nav.twkn',
quotes: 'nav.quotes'
quotes: 'nav.quotes',
bubble: 'nav.bubble'
}
}
@ -58,7 +59,8 @@ const TimelineMenu = {
currentUser: state => state.users.currentUser,
privateMode: state => state.instance.private,
federating: state => state.instance.federating,
bookmarkFolders: state => state.instance.pleromaBookmarkFoldersAvailable
bookmarkFolders: state => state.instance.pleromaBookmarkFoldersAvailable,
bubbleTimeline: state => state.instance.localBubbleInstances.length > 0
}),
timelinesList () {
return filterNavigation(
@ -68,7 +70,8 @@ const TimelineMenu = {
isFederating: this.federating,
isPrivate: this.privateMode,
currentUser: this.currentUser,
supportsBookmarkFolders: this.bookmarkFolders
supportsBookmarkFolders: this.bookmarkFolders,
supportsBubbleTimeline: this.bubbleTimeline
}
)
}

View file

@ -149,7 +149,10 @@ export default {
},
showModerationMenu () {
const privileges = this.loggedIn.privileges
return this.loggedIn.role === 'admin' || privileges.includes('users_manage_activation_state') || privileges.includes('users_delete') || privileges.includes('users_manage_tags')
return this.loggedIn.role === 'admin' ||
privileges.includes('users_manage_activation_state') ||
privileges.includes('users_delete') ||
privileges.includes('users_manage_tags')
},
hasNote () {
return this.relationship.note

View file

@ -81,7 +81,7 @@ const UserProfile = {
return this.isUs || !this.user.hide_followers
},
favoritesTabVisible () {
return this.isUs || !this.user.hide_favorites
return this.isUs || (this.$store.state.instance.pleromaPublicFavouritesAvailable && !this.user.hide_favorites)
},
formattedBirthday () {
const browserLocale = localeService.internalToBrowserLocale(this.$i18n.locale)

View file

@ -117,6 +117,7 @@
"flash_security": "Note that this can be potentially dangerous since Flash content is still arbitrary code.",
"flash_fail": "Failed to load flash content, see console for details.",
"scope_in_timeline": {
"local": "Non-federated",
"direct": "Direct",
"private": "Followers-only",
"public": "Public",
@ -171,6 +172,7 @@
"interactions": "Interactions",
"dms": "Direct messages",
"public_tl": "Public timeline",
"bubble": "Bubble timeline",
"timeline": "Timeline",
"home_timeline": "Home timeline",
"twkn": "Known Network",
@ -289,7 +291,8 @@
"text/plain": "Plain text",
"text/html": "HTML",
"text/markdown": "Markdown",
"text/bbcode": "BBCode"
"text/bbcode": "BBCode",
"text/x.misskeymarkdown": "MFM"
},
"content_type_selection": "Post format",
"content_warning": "Subject (optional)",
@ -1088,7 +1091,10 @@
"reset_value": "Reset",
"reset_value_tooltip": "Reset draft",
"hard_reset_value": "Hard reset",
"hard_reset_value_tooltip": "Remove setting from storage, forcing use of default value"
"hard_reset_value_tooltip": "Remove setting from storage, forcing use of default value",
"cache": "Cache",
"clear_asset_cache": "Clear asset cache",
"clear_emoji_cache": "Clear emoji cache"
},
"admin_dash": {
"window_title": "Administration",

View file

@ -211,6 +211,7 @@ const api = {
statusId = false,
bookmarkFolderId = false
}) {
if (timeline === 'favourites' && !store.rootState.instance.pleromaPublicFavouritesAvailable) return
if (store.state.fetchers[timeline]) return
const fetcher = store.state.backendInteractor.startFetchingTimeline({
@ -281,6 +282,7 @@ const api = {
// Bookmark folders
startFetchingBookmarkFolders (store) {
if (store.state.fetchers.bookmarkFolders) return
if (!store.rootState.instance.pleromaBookmarkFoldersAvailable) return
const fetcher = store.state.backendInteractor.startFetchingBookmarkFolders({ store })
store.commit('addFetcher', { fetcherName: 'bookmarkFolders', fetcher })
},

View file

@ -1,86 +0,0 @@
import { useOAuthStore } from 'src/stores/oauth.js'
const PASSWORD_STRATEGY = 'password'
const TOKEN_STRATEGY = 'token'
// MFA strategies
const TOTP_STRATEGY = 'totp'
const RECOVERY_STRATEGY = 'recovery'
// initial state
const state = {
settings: {},
strategy: PASSWORD_STRATEGY,
initStrategy: PASSWORD_STRATEGY // default strategy from config
}
const resetState = (state) => {
state.strategy = state.initStrategy
state.settings = {}
}
// getters
const getters = {
settings: (state) => {
return state.settings
},
requiredPassword: (state) => {
return state.strategy === PASSWORD_STRATEGY
},
requiredToken: (state) => {
return state.strategy === TOKEN_STRATEGY
},
requiredTOTP: (state) => {
return state.strategy === TOTP_STRATEGY
},
requiredRecovery: (state) => {
return state.strategy === RECOVERY_STRATEGY
}
}
// mutations
const mutations = {
setInitialStrategy (state, strategy) {
if (strategy) {
state.initStrategy = strategy
state.strategy = strategy
}
},
requirePassword (state) {
state.strategy = PASSWORD_STRATEGY
},
requireToken (state) {
state.strategy = TOKEN_STRATEGY
},
requireMFA (state, { settings }) {
state.settings = settings
state.strategy = TOTP_STRATEGY // default strategy of MFA
},
requireRecovery (state) {
state.strategy = RECOVERY_STRATEGY
},
requireTOTP (state) {
state.strategy = TOTP_STRATEGY
},
abortMFA (state) {
resetState(state)
}
}
// actions
const actions = {
async login ({ state, dispatch }, { access_token: accessToken }) {
useOAuthStore().setToken(accessToken)
await dispatch('loginUser', accessToken, { root: true })
resetState(state)
}
}
export default {
namespaced: true,
state,
getters,
mutations,
actions
}

View file

@ -6,7 +6,6 @@ import api from './api.js'
import config from './config.js'
import profileConfig from './profileConfig.js'
import adminSettings from './adminSettings.js'
import authFlow from './auth_flow.js'
import drafts from './drafts.js'
import chats from './chats.js'
@ -19,7 +18,6 @@ export default {
config,
profileConfig,
adminSettings,
authFlow,
drafts,
chats
}

View file

@ -143,7 +143,7 @@ const defaultState = {
emoji: {},
emojiFetched: false,
unicodeEmojiAnnotations: {},
pleromaBackend: true,
pleromaExtensionsAvailable: true,
postFormats: [],
restrictedNicknames: [],
safeDM: true,
@ -156,6 +156,8 @@ const defaultState = {
pleromaChatMessagesAvailable: false,
pleromaCustomEmojiReactionsAvailable: false,
pleromaBookmarkFoldersAvailable: false,
pleromaPublicFavouritesAvailable: true,
statusNotificationTypeAvailable: true,
gopherAvailable: false,
mediaProxyAvailable: false,
suggestionsEnabled: false,
@ -163,6 +165,7 @@ const defaultState = {
quotingAvailable: false,
groupActorAvailable: false,
blockExpiration: false,
localBubbleInstances: [], // Akkoma
// Html stuff
instanceSpecificPanelContent: '',
@ -341,7 +344,10 @@ const instance = {
async getCustomEmoji ({ commit, state }) {
try {
const res = await window.fetch('/api/pleroma/emoji.json')
let res = await window.fetch('/api/v1/pleroma/emoji')
if (!res.ok) {
res = await window.fetch('/api/pleroma/emoji.json')
}
if (res.ok) {
const result = await res.json()
const values = Array.isArray(result) ? Object.assign({}, ...result) : result

View file

@ -39,6 +39,7 @@ export const defaultState = () => ({
conversationsObject: {},
maxId: 0,
favorites: new Set(),
pleromaScrobblesAvailable: true, // not reported in nodeinfo
timelines: {
mentions: emptyTl(),
public: emptyTl(),
@ -50,7 +51,8 @@ export const defaultState = () => ({
tag: emptyTl(),
dms: emptyTl(),
bookmarks: emptyTl(),
list: emptyTl()
list: emptyTl(),
bubble: emptyTl()
}
})
@ -108,12 +110,21 @@ const sortTimeline = (timeline) => {
}
const getLatestScrobble = (state, user) => {
const scrobblesSupport = state.pleromaScrobblesAvailable
if (!scrobblesSupport) return
if (state.scrobblesNextFetch[user.id] && state.scrobblesNextFetch[user.id] > Date.now()) {
return
}
state.scrobblesNextFetch[user.id] = Date.now() + 24 * 60 * 60 * 1000
if (!scrobblesSupport) return
apiService.fetchScrobbles({ accountId: user.id }).then((scrobbles) => {
if (scrobbles?.error) {
state.pleromaScrobblesAvailable = false
return
}
if (scrobbles.length > 0) {
user.latestScrobble = scrobbles[0]

View file

@ -607,6 +607,7 @@ const users = {
return new Promise((resolve, reject) => {
const commit = store.commit
const dispatch = store.dispatch
const rootState = store.rootState
commit('beginLogin')
store.rootState.api.backendInteractor.verifyCredentials(accessToken)
.then((data) => {
@ -673,9 +674,11 @@ const users = {
// Start fetching notifications
dispatch('startFetchingNotifications')
if (rootState.instance.pleromaChatMessagesAvailable) {
// Start fetching chats
dispatch('startFetchingChats')
}
}
dispatch('startFetchingLists')
dispatch('startFetchingBookmarkFolders')

View file

@ -15,7 +15,7 @@ const TAG_USER_URL = '/api/pleroma/admin/users/tag'
const PERMISSION_GROUP_URL = (screenName, right) => `/api/pleroma/admin/users/${screenName}/permission_group/${right}`
const ACTIVATE_USER_URL = '/api/pleroma/admin/users/activate'
const DEACTIVATE_USER_URL = '/api/pleroma/admin/users/deactivate'
const ADMIN_USERS_URL = '/api/pleroma/admin/users'
const ADMIN_USERS_URL = '/api/v1/pleroma/admin/users'
const SUGGESTIONS_URL = '/api/v1/suggestions'
const NOTIFICATION_SETTINGS_URL = '/api/pleroma/notification_settings'
const NOTIFICATION_READ_URL = '/api/v1/pleroma/notifications/read'
@ -61,6 +61,7 @@ const MASTODON_LIST_TIMELINE_URL = id => `/api/v1/timelines/list/${id}`
const MASTODON_LIST_ACCOUNTS_URL = id => `/api/v1/lists/${id}/accounts`
const MASTODON_TAG_TIMELINE_URL = tag => `/api/v1/timelines/tag/${tag}`
const MASTODON_BOOKMARK_TIMELINE_URL = '/api/v1/bookmarks'
const AKKOMA_BUBBLE_TIMELINE_URL = '/api/v1/timelines/bubble'
const MASTODON_USER_BLOCKS_URL = '/api/v1/blocks/'
const MASTODON_USER_MUTES_URL = '/api/v1/mutes/'
const MASTODON_BLOCK_USER_URL = id => `/api/v1/accounts/${id}/block`
@ -99,7 +100,7 @@ const PLEROMA_CHAT_URL = id => `/api/v1/pleroma/chats/by-account-id/${id}`
const PLEROMA_CHAT_MESSAGES_URL = id => `/api/v1/pleroma/chats/${id}/messages`
const PLEROMA_CHAT_READ_URL = id => `/api/v1/pleroma/chats/${id}/read`
const PLEROMA_DELETE_CHAT_MESSAGE_URL = (chatId, messageId) => `/api/v1/pleroma/chats/${chatId}/messages/${messageId}`
const PLEROMA_ADMIN_REPORTS = '/api/pleroma/admin/reports'
const PLEROMA_ADMIN_REPORTS = '/api/v1/pleroma/admin/reports'
const PLEROMA_BACKUP_URL = '/api/v1/pleroma/backups'
const PLEROMA_ANNOUNCEMENTS_URL = '/api/v1/pleroma/admin/announcements'
const PLEROMA_POST_ANNOUNCEMENT_URL = '/api/v1/pleroma/admin/announcements'
@ -111,10 +112,10 @@ const PLEROMA_USER_FAVORITES_TIMELINE_URL = id => `/api/v1/pleroma/accounts/${id
const PLEROMA_BOOKMARK_FOLDERS_URL = '/api/v1/pleroma/bookmark_folders'
const PLEROMA_BOOKMARK_FOLDER_URL = id => `/api/v1/pleroma/bookmark_folders/${id}`
const PLEROMA_ADMIN_CONFIG_URL = '/api/pleroma/admin/config'
const PLEROMA_ADMIN_DESCRIPTIONS_URL = '/api/pleroma/admin/config/descriptions'
const PLEROMA_ADMIN_FRONTENDS_URL = '/api/pleroma/admin/frontends'
const PLEROMA_ADMIN_FRONTENDS_INSTALL_URL = '/api/pleroma/admin/frontends/install'
const PLEROMA_ADMIN_CONFIG_URL = '/api/v1/pleroma/admin/config'
const PLEROMA_ADMIN_DESCRIPTIONS_URL = '/api/v1/pleroma/admin/config/descriptions'
const PLEROMA_ADMIN_FRONTENDS_URL = '/api/v1/pleroma/admin/frontends'
const PLEROMA_ADMIN_FRONTENDS_INSTALL_URL = '/api/v1/pleroma/admin/frontends/install'
const PLEROMA_EMOJI_RELOAD_URL = '/api/pleroma/admin/reload_emoji'
const PLEROMA_EMOJI_IMPORT_FS_URL = '/api/pleroma/emoji/packs/import'
@ -714,7 +715,8 @@ const fetchTimeline = ({
publicFavorites: PLEROMA_USER_FAVORITES_TIMELINE_URL,
tag: MASTODON_TAG_TIMELINE_URL,
bookmarks: MASTODON_BOOKMARK_TIMELINE_URL,
quotes: PLEROMA_STATUS_QUOTES_URL
quotes: PLEROMA_STATUS_QUOTES_URL,
bubble: AKKOMA_BUBBLE_TIMELINE_URL
}
const isNotifications = timeline === 'notifications'
const params = []
@ -764,7 +766,7 @@ const fetchTimeline = ({
if (replyVisibility !== 'all') {
params.push(['reply_visibility', replyVisibility])
}
if (includeTypes.length > 0) {
if (includeTypes.size > 0) {
includeTypes.forEach(type => {
params.push(['include_types[]', type])
})

View file

@ -95,6 +95,32 @@ export const getContrastRatioLayers = (text, layers, bedrock) => {
return getContrastRatio(alphaBlendLayers(bedrock, layers), text)
}
/**
* Blending of two solid colors with a user-defined operator: origin +- value
*
* @param {Object} origin - base color
* @param {Object} value - modification argument
* @param {string} operator - math operator to use
*/
export const arithmeticBlend = (origin, value, operator) => {
const func = (a, b) => {
switch (operator) {
case '+':
return Math.min(a + b, 255)
case '-':
return Math.max(a - b, 0)
default:
return a
}
}
return {
r: func(origin.r, value.r),
g: func(origin.g, value.g),
b: func(origin.b, value.b),
}
}
/**
* This performs alpha blending between solid background and semi-transparent foreground
*

View file

@ -91,6 +91,8 @@ export const parseUser = (data) => {
output.bot = data.bot
output.privileges = []
if (data.pleroma) {
if (data.pleroma.settings_store) {
output.storage = data.pleroma.settings_store['pleroma-fe']
@ -317,20 +319,18 @@ export const parseStatus = (data) => {
output.edited_at = data.edited_at
if (data.pleroma) {
const { pleroma } = data
if (data.pleroma) {
output.text = pleroma.content ? data.pleroma.content['text/plain'] : data.content
output.summary = pleroma.spoiler_text ? data.pleroma.spoiler_text['text/plain'] : data.spoiler_text
output.statusnet_conversation_id = data.pleroma.conversation_id
output.is_local = pleroma.local
output.in_reply_to_screen_name = data.pleroma.in_reply_to_account_acct
output.in_reply_to_screen_name = pleroma.in_reply_to_account_acct
output.thread_muted = pleroma.thread_muted
output.emoji_reactions = pleroma.emoji_reactions
output.parent_visible = pleroma.parent_visible === undefined ? true : pleroma.parent_visible
output.quote = pleroma.quote ? parseStatus(pleroma.quote) : undefined
output.quote_id = pleroma.quote_id ? pleroma.quote_id : (output.quote ? output.quote.id : undefined)
output.quote_url = pleroma.quote_url
output.quote_visible = pleroma.quote_visible
output.quote_visible = pleroma.quote_visible || true
output.quotes_count = pleroma.quotes_count
output.bookmark_folder_id = pleroma.bookmark_folder
} else {
@ -338,6 +338,12 @@ export const parseStatus = (data) => {
output.summary = data.spoiler_text
}
const quoteRaw = pleroma?.quote || data.quote
const quoteData = quoteRaw ? parseStatus(quoteRaw) : undefined
output.quote = quoteData
output.quote_id = data.quote?.id ?? data.quote_id ?? quoteData?.id ?? pleroma.quote_id
output.quote_url = data.quote?.url ?? quoteData?.url ?? pleroma.quote_url
output.in_reply_to_status_id = data.in_reply_to_id
output.in_reply_to_user_id = data.in_reply_to_account_id
output.replies_count = data.replies_count

View file

@ -2,9 +2,13 @@ import { useInterfaceStore } from 'src/stores/interface.js'
import apiService from '../api/api.service.js'
import { promiseInterval } from '../promise_interval/promise_interval.js'
const update = ({ store, notifications, older }) => {
store.dispatch('addNewNotifications', { notifications, older })
}
//
// For using include_types when fetching notifications.
// Note: chat_mention excluded as pleroma-fe polls them separately
const mastoApiNotificationTypes = [
const mastoApiNotificationTypes = new Set([
'mention',
'status',
'favourite',
@ -14,21 +18,22 @@ const mastoApiNotificationTypes = [
'move',
'poll',
'pleroma:emoji_reaction',
'pleroma:chat_mention',
'pleroma:report'
]
const update = ({ store, notifications, older }) => {
store.dispatch('addNewNotifications', { notifications, older })
}
'pleroma:report',
'test'
])
const fetchAndUpdate = ({ store, credentials, older = false, since }) => {
const args = { credentials }
const { getters } = store
const rootState = store.rootState || store.state
const timelineData = rootState.notifications
const hideMutedPosts = getters.mergedConfig.hideMutedPosts
if (rootState.instance.pleromaChatMessagesAvailable) {
mastoApiNotificationTypes.add('pleroma:chat_mention')
}
args.includeTypes = mastoApiNotificationTypes
args.withMuted = !hideMutedPosts
@ -72,8 +77,18 @@ const fetchNotifications = ({ store, args, older }) => {
return apiService.fetchTimeline(args)
.then((response) => {
if (response.errors) {
if (response.status === 400 && response.statusText.includes('Invalid value for enum')) {
response
.statusText
.matchAll(/(\w+) - Invalid value for enum./g)
.toArray()
.map(x => x[1])
.forEach(x => mastoApiNotificationTypes.delete(x))
return fetchNotifications({ store, args, older })
} else {
throw new Error(`${response.status} ${response.statusText}`)
}
}
const notifications = response.data
update({ store, notifications, older })
return notifications

View file

@ -1,47 +1,76 @@
import { init, getEngineChecksum } from '../theme_data/theme_data_3.service.js'
import { getCssRules } from '../theme_data/css_utils.js'
import { defaultState } from 'src/modules/default_config_state.js'
import { chunk } from 'lodash'
import { chunk, throttle } from 'lodash'
import localforage from 'localforage'
// On platforms where this is not supported, it will return undefined
// Otherwise it will return an array
const supportsAdoptedStyleSheets = !!document.adoptedStyleSheets
const createStyleSheet = (id) => {
const stylesheets = {}
export const createStyleSheet = (id, priority = 1000) => {
if (stylesheets[id]) return stylesheets[id]
const newStyleSheet = {
rules: [],
ready: false,
priority,
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) {
return {
el: null,
sheet: new CSSStyleSheet(),
rules: []
}
document.adoptedStyleSheets = Object
.values(stylesheets)
.filter(x => x.ready)
.sort((a, b) => a.priority - b.priority)
.map(sheet => {
const css = new CSSStyleSheet()
sheet.rules.forEach(r => css.insertRule(r))
return css
})
} else {
const holder = document.getElementById('custom-styles-holder')
for (let i = holder.sheet.cssRules.length - 1; i >= 0; --i) {
holder.sheet.deleteRule(i)
}
const el = document.getElementById(id)
// Clear all rules in it
for (let i = el.sheet.cssRules.length - 1; i >= 0; --i) {
el.sheet.deleteRule(i)
}
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)
Object
.values(stylesheets)
.filter(x => x.ready)
.sort((a, b) => a.priority - b.priority)
.forEach(sheet => {
sheet.rules.forEach(r => holder.sheet.insertRule(r))
})
}
// Some older browsers do not support document.adoptedStyleSheets.
// In this case, we use the <style> elements.
// Since the <style> elements we need are already in the DOM, there
// 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) => {
const {
@ -94,13 +123,17 @@ export const tryLoadCache = async () => {
if (!cache) return null
try {
if (cache.engineChecksum === getEngineChecksum()) {
const eagerStyles = createStyleSheet(EAGER_STYLE_ID)
const lazyStyles = createStyleSheet(LAZY_STYLE_ID)
const eagerStyles = createStyleSheet(EAGER_STYLE_ID, 10)
const lazyStyles = createStyleSheet(LAZY_STYLE_ID, 20)
cache.data[0].forEach(rule => eagerStyles.sheet.insertRule(rule, 'index-max'))
cache.data[1].forEach(rule => lazyStyles.sheet.insertRule(rule, 'index-max'))
cache.data[0].forEach(rule => eagerStyles.addRule(rule))
cache.data[1].forEach(rule => lazyStyles.addRule(rule))
adoptStyleSheets([eagerStyles, lazyStyles])
eagerStyles.ready = true
lazyStyles.ready = true
// Don't do this, we need to wait until config adopts its styles first
//adoptStyleSheets()
console.info(`Loaded theme from cache`)
return true
@ -120,57 +153,29 @@ export const applyTheme = (
onFinish = () => {},
debug
) => {
const eagerStyles = createStyleSheet(EAGER_STYLE_ID)
const lazyStyles = createStyleSheet(LAZY_STYLE_ID)
const eagerStyles = createStyleSheet(EAGER_STYLE_ID, 10)
const lazyStyles = createStyleSheet(LAZY_STYLE_ID, 20)
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(
input,
{
onNewRule (rule, isLazy) {
if (isLazy) {
insertRule(lazyStyles, rule)
lazyStyles.addRule(rule)
} else {
insertRule(eagerStyles, rule)
eagerStyles.addRule(rule)
}
},
onEagerFinished () {
adoptStyleSheets([eagerStyles])
eagerStyles.ready = true
adoptStyleSheets()
onEagerFinish()
console.info('Eager part of theme finished, waiting for lazy part to finish to store cache')
},
onLazyFinished () {
adoptStyleSheets([eagerStyles, lazyStyles])
lazyStyles.ready = true
adoptStyleSheets()
const cache = { engineChecksum: getEngineChecksum(), data: [eagerStyles.rules, lazyStyles.rules] }
onFinish(cache)
localforage.setItem('pleromafe-theme-cache', cache)
@ -234,28 +239,23 @@ export const applyConfig = (input) => {
return
}
const head = document.head
const rules = Object
.entries(config)
.filter(([, v]) => v)
.map(([k, v]) => `--${k}: ${v}`).join(';')
document.getElementById('style-config')?.remove()
const styleEl = document.createElement('style')
styleEl.id = 'style-config'
head.appendChild(styleEl)
const styleSheet = styleEl.sheet
const styleSheet = createStyleSheet('theme-holder', 30)
styleSheet.toString()
styleSheet.insertRule(`:root { ${rules} }`, 'index-max')
styleSheet.addRule(`:root { ${rules} }`)
// TODO find a way to make this not apply to theme previews
if (Object.prototype.hasOwnProperty.call(config, 'forcedRoundness')) {
styleSheet.insertRule(` *:not(.preview-block) {
styleSheet.addRule(` *:not(.preview-block) {
--roundness: var(--forcedRoundness) !important;
}`, 'index-max')
}`)
}
styleSheet.ready = true
adoptStyleSheets()
}
export const getResourcesIndex = async (url, parser = JSON.parse) => {

View file

@ -18,6 +18,7 @@ function isPushSupported () {
}
function getOrCreateServiceWorker () {
if (!isSWSupported()) return
const swType = process.env.HAS_MODULE_SERVICE_WORKER ? 'module' : 'classic'
return navigator.serviceWorker.register('/sw-pleroma.js', { type: swType })
.catch((err) => console.error('Unable to get or create a service worker.', err))
@ -146,3 +147,11 @@ export function unregisterPushNotifications (token) {
]).catch((e) => console.warn(`Failed to disable Web Push Notifications: ${e.message}`))
}
}
export const shouldCache = process.env.NODE_ENV === 'production'
export const cacheKey = 'pleroma-fe'
export const emojiCacheKey = 'pleroma-fe-emoji'
export const clearCache = (key) => caches.delete(key)
export { getOrCreateServiceWorker }

View file

@ -1,8 +1,8 @@
import { convert, brightness } from 'chromatism'
import { alphaBlend, getTextColor, relativeLuminance } from '../color_convert/color_convert.js'
import { alphaBlend, arithmeticBlend, getTextColor, relativeLuminance } from '../color_convert/color_convert.js'
export const process = (text, functions, { findColor, findShadow }, { dynamicVars, staticVars }) => {
const { funcName, argsString } = /\$(?<funcName>\w+)\((?<argsString>[#a-zA-Z0-9-,.'"\s]*)\)/.exec(text).groups
const { funcName, argsString } = /\$(?<funcName>\w+)\((?<argsString>[#a-zA-Z0-9-+,.'"\s]*)\)/.exec(text).groups
const args = argsString.split(/ /g).map(a => a.trim())
const func = functions[funcName]
@ -81,6 +81,23 @@ export const colorFunctions = {
return alphaBlend(background, amount, foreground)
}
},
shift: {
argsNeeded: 2,
documentation: 'Arithmetic blend between two colors',
args: [
'origin: base color',
'value: shift value',
'operator: math operator to use (+ or -)'
],
exec: (args, { findColor }, { dynamicVars, staticVars }) => {
const [originArg, valueArg, operatorArg] = args
const origin = convert(findColor(originArg, { dynamicVars, staticVars })).rgb
const value = convert(findColor(valueArg, { dynamicVars, staticVars })).rgb
return arithmeticBlend(origin, value, operatorArg)
}
},
boost: {
argsNeeded: 2,
documentation: 'If given color is dark makes it darker, if color is light - makes it lighter',

View file

@ -54,7 +54,7 @@ const fetchAndUpdate = ({
args.bookmarkFolderId = bookmarkFolderId
args.tag = tag
args.withMuted = !hideMutedPosts
if (loggedIn && ['friends', 'public', 'publicAndExternal'].includes(timeline)) {
if (loggedIn && ['friends', 'public', 'publicAndExternal', 'bubble'].includes(timeline)) {
args.replyVisibility = replyVisibility
}
@ -63,6 +63,10 @@ const fetchAndUpdate = ({
return apiService.fetchTimeline(args)
.then(response => {
if (response.errors) {
if (timeline === 'favorites') {
rootState.instance.pleromaPublicFavouritesAvailable = false
return
}
throw new Error(`${response.status} ${response.statusText}`)
}

69
src/stores/auth_flow.js Normal file
View file

@ -0,0 +1,69 @@
import { useOAuthStore } from 'src/stores/oauth.js'
import { defineStore } from 'pinia'
const PASSWORD_STRATEGY = 'password'
const TOKEN_STRATEGY = 'token'
// MFA strategies
const TOTP_STRATEGY = 'totp'
const RECOVERY_STRATEGY = 'recovery'
export const useAuthFlowStore = defineStore('authFlow', {
// initial state
state: () => ({
settings: {},
strategy: PASSWORD_STRATEGY,
initStrategy: PASSWORD_STRATEGY // default strategy from config
}),
// getters
getters: {
requiredPassword: (state) => {
return state.strategy === PASSWORD_STRATEGY
},
requiredToken: (state) => {
return state.strategy === TOKEN_STRATEGY
},
requiredTOTP: (state) => {
return state.strategy === TOTP_STRATEGY
},
requiredRecovery: (state) => {
return state.strategy === RECOVERY_STRATEGY
},
},
actions: {
setInitialStrategy (strategy) {
if (strategy) {
this.initStrategy = strategy
this.strategy = strategy
}
},
requirePassword () {
this.strategy = PASSWORD_STRATEGY
},
requireToken () {
this.strategy = TOKEN_STRATEGY
},
requireMFA ({ settings }) {
this.settings = settings
this.strategy = TOTP_STRATEGY // default strategy of MFA
},
requireRecovery () {
this.strategy = RECOVERY_STRATEGY
},
requireTOTP () {
this.strategy = TOTP_STRATEGY
},
abortMFA () {
this.resetState()
},
resetState () {
this.strategy = this.initStrategy
this.settings = {}
},
async login ({ access_token: accessToken }) {
useOAuthStore().setToken(accessToken)
await window.vuex.dispatch('loginUser', accessToken, { root: true })
this.resetState()
}
}
})

109
src/sw.js
View file

@ -1,8 +1,10 @@
/* eslint-env serviceworker */
import 'virtual:pleroma-fe/service_worker_env'
import { storage } from 'src/lib/storage.js'
import { parseNotification } from './services/entity_normalizer/entity_normalizer.service.js'
import { prepareNotificationObject } from './services/notification_utils/notification_utils.js'
import { shouldCache, cacheKey, emojiCacheKey } from './services/sw/sw.js'
import { createI18n } from 'vue-i18n'
// Collects all messages for service workers
// Needed because service workers cannot use dynamic imports
@ -85,6 +87,80 @@ const showPushNotification = async (event) => {
return Promise.resolve()
}
const cacheFiles = self.serviceWorkerOption.assets
const isEmoji = req => {
if (req.method !== 'GET') {
return false
}
const url = new URL(req.url)
return url.pathname.startsWith('/emoji/')
}
const isNotMedia = req => {
if (req.method !== 'GET') {
return false
}
const url = new URL(req.url)
return !url.pathname.startsWith('/media/')
}
const isAsset = req => {
const url = new URL(req.url)
return cacheFiles.includes(url.pathname)
}
const isSuccessful = (resp) => {
if (!resp.ok) {
return false
}
if ((new URL(resp.url)).pathname === '/index.html') {
// For index.html itself, there is no fallback possible.
return true
}
const type = resp.headers.get('Content-Type')
// Backend will revert to index.html if the file does not exist, so text/html for emojis and assets is a failure
return type && !type.includes('text/html')
}
self.addEventListener('install', async (event) => {
if (shouldCache) {
event.waitUntil((async () => {
// Do not preload i18n and emoji annotations to speed up loading
const shouldPreload = (route) => {
return !route.startsWith('/static/js/i18n/') && !route.startsWith('/static/js/emoji-annotations/')
}
const cache = await caches.open(cacheKey)
await Promise.allSettled(cacheFiles.filter(shouldPreload).map(async (route) => {
// https://developer.mozilla.org/en-US/docs/Web/API/Cache/add
// originally we used addAll() but it will raise a problem in one edge case:
// when the file for the route is not found, backend will return index.html with code 200
// but it's wrong, and it's cached, so we end up with a bad cache.
// this can happen when you refresh when you are in the process of upgrading
// the frontend.
const resp = await fetch(route)
if (isSuccessful(resp)) {
await cache.put(route, resp)
}
}))
})())
}
})
self.addEventListener('activate', async (event) => {
if (shouldCache) {
event.waitUntil((async () => {
const cache = await caches.open(cacheKey)
const keys = await cache.keys()
await Promise.all(
keys.filter(request => {
const url = new URL(request.url)
const shouldKeep = cacheFiles.includes(url.pathname)
return !shouldKeep
}).map(k => cache.delete(k))
)
})())
}
})
self.addEventListener('push', async (event) => {
if (event.data) {
// Supposedly, we HAVE to return a promise inside waitUntil otherwise it will
@ -143,4 +219,35 @@ self.addEventListener('notificationclick', (event) => {
}))
})
console.log('sw here')
self.addEventListener('fetch', (event) => {
// Do not mess up with remote things
const isSameOrigin = (new URL(event.request.url)).origin === self.location.origin
if (shouldCache && event.request.method === 'GET' && isSameOrigin && isNotMedia(event.request)) {
// this is a bit spammy
// console.debug('[Service worker] fetch:', event.request.url)
event.respondWith((async () => {
const r = await caches.match(event.request)
const isEmojiReq = isEmoji(event.request)
if (r && isSuccessful(r)) {
console.debug('[Service worker] already cached:', event.request.url)
return r
}
try {
const response = await fetch(event.request)
if (response.ok &&
isSuccessful(response) &&
(isEmojiReq || isAsset(event.request))) {
console.debug(`[Service worker] caching ${isEmojiReq ? 'emoji' : 'asset'}:`, event.request.url)
const cache = await caches.open(isEmojiReq ? emojiCacheKey : cacheKey)
await cache.put(event.request.clone(), response.clone())
}
return response
} catch (e) {
console.error('[Service worker] error when caching emoji:', e)
throw e
}
})())
}
})

View file

@ -172,7 +172,14 @@ export default defineConfig(async ({ mode, command }) => {
return 'static/js/[name].[hash].js'
}
},
chunkFileNames () {
chunkFileNames (chunkInfo) {
if (chunkInfo.facadeModuleId) {
if (chunkInfo.facadeModuleId.includes('node_modules/@kazvmoe-infra/unicode-emoji-json/annotations/')) {
return 'static/js/emoji-annotations/[name].[hash].js'
} else if (chunkInfo.facadeModuleId.includes('src/i18n/')) {
return 'static/js/i18n/[name].[hash].js'
}
}
return 'static/js/[name].[hash].js'
},
assetFileNames (assetInfo) {

334
yarn.lock
View file

@ -411,6 +411,13 @@
dependencies:
"@babel/types" "^7.27.1"
"@babel/parser@^7.27.5":
version "7.27.5"
resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.27.5.tgz#ed22f871f110aa285a6fd934a0efed621d118826"
integrity sha512-OsQd175SxWkGlzbny8J3K8TnnDD0N3lrIUtB92xwyRpzaenGZhxDvxN/JgU00U3CDZNj9tPuDJ5H0WS4Nt3vKg==
dependencies:
"@babel/types" "^7.27.3"
"@babel/plugin-bugfix-firefox-class-in-computed-class-key@^7.27.1":
version "7.27.1"
resolved "https://registry.yarnpkg.com/@babel/plugin-bugfix-firefox-class-in-computed-class-key/-/plugin-bugfix-firefox-class-in-computed-class-key-7.27.1.tgz#61dd8a8e61f7eb568268d1b5f129da3eee364bf9"
@ -1061,6 +1068,14 @@
"@babel/helper-string-parser" "^7.27.1"
"@babel/helper-validator-identifier" "^7.27.1"
"@babel/types@^7.27.3":
version "7.27.6"
resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.27.6.tgz#a434ca7add514d4e646c80f7375c0aa2befc5535"
integrity sha512-ETyHEk2VHHvl9b9jZP5IHPavHYk57EhanlRRuae9XCpb/j5bDCbPPMOBfCWhnl/7EDJz0jEMCi/RhccCE8r1+Q==
dependencies:
"@babel/helper-string-parser" "^7.27.1"
"@babel/helper-validator-identifier" "^7.27.1"
"@bazel/runfiles@^6.3.1":
version "6.3.1"
resolved "https://registry.yarnpkg.com/@bazel/runfiles/-/runfiles-6.3.1.tgz#3f8824b2d82853377799d42354b4df78ab0ace0b"
@ -1587,10 +1602,10 @@
"@jridgewell/resolve-uri" "^3.1.0"
"@jridgewell/sourcemap-codec" "^1.4.14"
"@kazvmoe-infra/pinch-zoom-element@1.2.0":
version "1.2.0"
resolved "https://registry.yarnpkg.com/@kazvmoe-infra/pinch-zoom-element/-/pinch-zoom-element-1.2.0.tgz#eb3ca34c53b4410c689d60aca02f4a497ce84aba"
integrity sha512-HBrhH5O/Fsp2bB7EGTXzCsBAVcMjknSagKC5pBdGpKsF8meHISR0kjDIdw4YoE0S+0oNMwJ6ZUZyIBrdywxPPw==
"@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"
@ -1622,10 +1637,10 @@
zod "^3.23.8"
zod-to-json-schema "^3.24.1"
"@mswjs/interceptors@^0.37.0":
version "0.37.6"
resolved "https://registry.yarnpkg.com/@mswjs/interceptors/-/interceptors-0.37.6.tgz#2635319b7a81934e1ef1b5593ef7910347e2b761"
integrity sha512-wK+5pLK5XFmgtH3aQ2YVvA3HohS3xqV/OxuVOdNx9Wpnz7VE/fnC+e1A7ln6LFYeck7gOJ/dsZV6OLplOtAJ2w==
"@mswjs/interceptors@^0.39.1":
version "0.39.2"
resolved "https://registry.yarnpkg.com/@mswjs/interceptors/-/interceptors-0.39.2.tgz#de9de0ab23f99d387c7904df7219a92157d1d666"
integrity sha512-RuzCup9Ct91Y7V79xwCb146RaBRHZ7NBbrIUySumd1rpKqHL5OonaqrGIbug5hNwP/fRyxFMA6ISgw4FTtYFYg==
dependencies:
"@open-draft/deferred-promise" "^2.2.0"
"@open-draft/logger" "^0.3.0"
@ -2309,6 +2324,17 @@
estree-walker "^2.0.2"
source-map-js "^1.2.0"
"@vue/compiler-core@3.5.17":
version "3.5.17"
resolved "https://registry.yarnpkg.com/@vue/compiler-core/-/compiler-core-3.5.17.tgz#23d291bd01b863da3ef2e26e7db84d8e01a9b4c5"
integrity sha512-Xe+AittLbAyV0pabcN7cP7/BenRBNcteM4aSDCtRvGw0d9OL+HG1u/XHLY/kt1q4fyMeZYXyIYrsHuPSiDPosA==
dependencies:
"@babel/parser" "^7.27.5"
"@vue/shared" "3.5.17"
entities "^4.5.0"
estree-walker "^2.0.2"
source-map-js "^1.2.1"
"@vue/compiler-dom@3.5.13":
version "3.5.13"
resolved "https://registry.yarnpkg.com/@vue/compiler-dom/-/compiler-dom-3.5.13.tgz#bb1b8758dbc542b3658dda973b98a1c9311a8a58"
@ -2317,7 +2343,30 @@
"@vue/compiler-core" "3.5.13"
"@vue/shared" "3.5.13"
"@vue/compiler-sfc@3.5.13", "@vue/compiler-sfc@^3.5.13":
"@vue/compiler-dom@3.5.17":
version "3.5.17"
resolved "https://registry.yarnpkg.com/@vue/compiler-dom/-/compiler-dom-3.5.17.tgz#7bc19a20e23b670243a64b47ce3a890239b870be"
integrity sha512-+2UgfLKoaNLhgfhV5Ihnk6wB4ljyW1/7wUIog2puUqajiC29Lp5R/IKDdkebh9jTbTogTbsgB+OY9cEWzG95JQ==
dependencies:
"@vue/compiler-core" "3.5.17"
"@vue/shared" "3.5.17"
"@vue/compiler-sfc@3.5.17":
version "3.5.17"
resolved "https://registry.yarnpkg.com/@vue/compiler-sfc/-/compiler-sfc-3.5.17.tgz#c518871276e26593612bdab36f3f5bcd053b13bf"
integrity sha512-rQQxbRJMgTqwRugtjw0cnyQv9cP4/4BxWfTdRBkqsTfLOHWykLzbOc3C4GGzAmdMDxhzU/1Ija5bTjMVrddqww==
dependencies:
"@babel/parser" "^7.27.5"
"@vue/compiler-core" "3.5.17"
"@vue/compiler-dom" "3.5.17"
"@vue/compiler-ssr" "3.5.17"
"@vue/shared" "3.5.17"
estree-walker "^2.0.2"
magic-string "^0.30.17"
postcss "^8.5.6"
source-map-js "^1.2.1"
"@vue/compiler-sfc@^3.5.13":
version "3.5.13"
resolved "https://registry.yarnpkg.com/@vue/compiler-sfc/-/compiler-sfc-3.5.13.tgz#461f8bd343b5c06fac4189c4fef8af32dea82b46"
integrity sha512-6VdaljMpD82w6c2749Zhf5T9u5uLBWKnVue6XWxprDobftnletJ8+oel7sexFfM3qIxNmVE7LSFGTpv6obNyaQ==
@ -2340,6 +2389,14 @@
"@vue/compiler-dom" "3.5.13"
"@vue/shared" "3.5.13"
"@vue/compiler-ssr@3.5.17":
version "3.5.17"
resolved "https://registry.yarnpkg.com/@vue/compiler-ssr/-/compiler-ssr-3.5.17.tgz#14ba3b7bba6e0e1fd02002316263165a5d1046c7"
integrity sha512-hkDbA0Q20ZzGgpj5uZjb9rBzQtIHLS78mMilwrlpWk2Ep37DYntUz0PonQ6kr113vfOEdM+zTBuJDaceNIW0tQ==
dependencies:
"@vue/compiler-dom" "3.5.17"
"@vue/shared" "3.5.17"
"@vue/devtools-api@^6.0.0-beta.11", "@vue/devtools-api@^6.5.0", "@vue/devtools-api@^6.6.4":
version "6.6.4"
resolved "https://registry.yarnpkg.com/@vue/devtools-api/-/devtools-api-6.6.4.tgz#cbe97fe0162b365edc1dba80e173f90492535343"
@ -2372,44 +2429,49 @@
dependencies:
rfdc "^1.4.1"
"@vue/reactivity@3.5.13":
version "3.5.13"
resolved "https://registry.yarnpkg.com/@vue/reactivity/-/reactivity-3.5.13.tgz#b41ff2bb865e093899a22219f5b25f97b6fe155f"
integrity sha512-NaCwtw8o48B9I6L1zl2p41OHo/2Z4wqYGGIK1Khu5T7yxrn+ATOixn/Udn2m+6kZKB/J7cuT9DbWWhRxqixACg==
"@vue/reactivity@3.5.17":
version "3.5.17"
resolved "https://registry.yarnpkg.com/@vue/reactivity/-/reactivity-3.5.17.tgz#169b5dcf96c7f23788e5ed9745ec8a7227f2125e"
integrity sha512-l/rmw2STIscWi7SNJp708FK4Kofs97zc/5aEPQh4bOsReD/8ICuBcEmS7KGwDj5ODQLYWVN2lNibKJL1z5b+Lw==
dependencies:
"@vue/shared" "3.5.13"
"@vue/shared" "3.5.17"
"@vue/runtime-core@3.5.13":
version "3.5.13"
resolved "https://registry.yarnpkg.com/@vue/runtime-core/-/runtime-core-3.5.13.tgz#1fafa4bf0b97af0ebdd9dbfe98cd630da363a455"
integrity sha512-Fj4YRQ3Az0WTZw1sFe+QDb0aXCerigEpw418pw1HBUKFtnQHWzwojaukAs2X/c9DQz4MQ4bsXTGlcpGxU/RCIw==
"@vue/runtime-core@3.5.17":
version "3.5.17"
resolved "https://registry.yarnpkg.com/@vue/runtime-core/-/runtime-core-3.5.17.tgz#b17bd41e13011e85e9b1025545292d43f5512730"
integrity sha512-QQLXa20dHg1R0ri4bjKeGFKEkJA7MMBxrKo2G+gJikmumRS7PTD4BOU9FKrDQWMKowz7frJJGqBffYMgQYS96Q==
dependencies:
"@vue/reactivity" "3.5.13"
"@vue/shared" "3.5.13"
"@vue/reactivity" "3.5.17"
"@vue/shared" "3.5.17"
"@vue/runtime-dom@3.5.13":
version "3.5.13"
resolved "https://registry.yarnpkg.com/@vue/runtime-dom/-/runtime-dom-3.5.13.tgz#610fc795de9246300e8ae8865930d534e1246215"
integrity sha512-dLaj94s93NYLqjLiyFzVs9X6dWhTdAlEAciC3Moq7gzAc13VJUdCnjjRurNM6uTLFATRHexHCTu/Xp3eW6yoog==
"@vue/runtime-dom@3.5.17":
version "3.5.17"
resolved "https://registry.yarnpkg.com/@vue/runtime-dom/-/runtime-dom-3.5.17.tgz#8e325e29cd03097fe179032fc8df384a426fc83a"
integrity sha512-8El0M60TcwZ1QMz4/os2MdlQECgGoVHPuLnQBU3m9h3gdNRW9xRmI8iLS4t/22OQlOE6aJvNNlBiCzPHur4H9g==
dependencies:
"@vue/reactivity" "3.5.13"
"@vue/runtime-core" "3.5.13"
"@vue/shared" "3.5.13"
"@vue/reactivity" "3.5.17"
"@vue/runtime-core" "3.5.17"
"@vue/shared" "3.5.17"
csstype "^3.1.3"
"@vue/server-renderer@3.5.13":
version "3.5.13"
resolved "https://registry.yarnpkg.com/@vue/server-renderer/-/server-renderer-3.5.13.tgz#429ead62ee51de789646c22efe908e489aad46f7"
integrity sha512-wAi4IRJV/2SAW3htkTlB+dHeRmpTiVIK1OGLWV1yeStVSebSQQOwGwIq0D3ZIoBj2C2qpgz5+vX9iEBkTdk5YA==
"@vue/server-renderer@3.5.17":
version "3.5.17"
resolved "https://registry.yarnpkg.com/@vue/server-renderer/-/server-renderer-3.5.17.tgz#9b8fd6a40a3d55322509fafe78ac841ede649fbe"
integrity sha512-BOHhm8HalujY6lmC3DbqF6uXN/K00uWiEeF22LfEsm9Q93XeJ/plHTepGwf6tqFcF7GA5oGSSAAUock3VvzaCA==
dependencies:
"@vue/compiler-ssr" "3.5.13"
"@vue/shared" "3.5.13"
"@vue/compiler-ssr" "3.5.17"
"@vue/shared" "3.5.17"
"@vue/shared@3.5.13", "@vue/shared@^3.5.13":
version "3.5.13"
resolved "https://registry.yarnpkg.com/@vue/shared/-/shared-3.5.13.tgz#87b309a6379c22b926e696893237826f64339b6f"
integrity sha512-/hnE/qP5ZoGpol0a5mDi45bOd7t3tjYJBjsgCsivow7D48cJeV5l05RD82lPqi7gRiphZM37rnhW1l6ZoCNNnQ==
"@vue/shared@3.5.17":
version "3.5.17"
resolved "https://registry.yarnpkg.com/@vue/shared/-/shared-3.5.17.tgz#e8b3a41f0be76499882a89e8ed40d86a70fa4b70"
integrity sha512-CabR+UN630VnsJO/jHWYBC1YVXyMq94KKp6iF5MQgZJs5I8cmjw6oVMO1oDbtBkENSHSSn/UadWlW/OAgdmKrg==
"@vue/test-utils@2.4.6":
version "2.4.6"
resolved "https://registry.yarnpkg.com/@vue/test-utils/-/test-utils-2.4.6.tgz#7d534e70c4319d2a587d6a3b45a39e9695ade03c"
@ -3348,17 +3410,6 @@ cross-spawn@7.0.6, cross-spawn@^7.0.3, cross-spawn@^7.0.6:
shebang-command "^2.0.0"
which "^2.0.1"
cross-spawn@^6.0.0:
version "6.0.6"
resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-6.0.6.tgz#30d0efa0712ddb7eb5a76e1e8721bffafa6b5d57"
integrity sha512-VqCUuhcd1iB+dsv8gxPttb5iZh/D0iubSP21g36KXdEuf6I5JiioesUVjpCdHV9MZRUfVFlvwtIUyPfxo5trtw==
dependencies:
nice-try "^1.0.4"
path-key "^2.0.1"
semver "^5.5.0"
shebang-command "^1.2.0"
which "^1.2.9"
css-functions-list@^3.2.3:
version "3.2.3"
resolved "https://registry.yarnpkg.com/css-functions-list/-/css-functions-list-3.2.3.tgz#95652b0c24f0f59b291a9fc386041a19d4f40dbe"
@ -4199,18 +4250,20 @@ eventsource@^3.0.2:
dependencies:
eventsource-parser "^3.0.1"
execa@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/execa/-/execa-1.0.0.tgz#c6236a5bb4df6d6f15e88e7f017798216749ddd8"
integrity sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA==
execa@^5.1.1:
version "5.1.1"
resolved "https://registry.yarnpkg.com/execa/-/execa-5.1.1.tgz#f80ad9cbf4298f7bd1d4c9555c21e93741c411dd"
integrity sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==
dependencies:
cross-spawn "^6.0.0"
get-stream "^4.0.0"
is-stream "^1.1.0"
npm-run-path "^2.0.0"
p-finally "^1.0.0"
signal-exit "^3.0.0"
strip-eof "^1.0.0"
cross-spawn "^7.0.3"
get-stream "^6.0.0"
human-signals "^2.1.0"
is-stream "^2.0.0"
merge-stream "^2.0.0"
npm-run-path "^4.0.1"
onetime "^5.1.2"
signal-exit "^3.0.3"
strip-final-newline "^2.0.0"
expect-type@^1.2.1:
version "1.2.1"
@ -4556,13 +4609,6 @@ get-proto@^1.0.0, get-proto@^1.0.1:
dunder-proto "^1.0.1"
es-object-atoms "^1.0.0"
get-stream@^4.0.0:
version "4.1.0"
resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-4.1.0.tgz#c1b255575f3dc21d59bfc79cd3d2b46b1c3a54b5"
integrity sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==
dependencies:
pump "^3.0.0"
get-stream@^5.1.0:
version "5.2.0"
resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-5.2.0.tgz#4966a1795ee5ace65e706c4b7beb71257d6e22d3"
@ -4570,6 +4616,11 @@ get-stream@^5.1.0:
dependencies:
pump "^3.0.0"
get-stream@^6.0.0:
version "6.0.1"
resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-6.0.1.tgz#a262d8eef67aced57c2852ad6167526a43cbf7b7"
integrity sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==
get-symbol-description@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/get-symbol-description/-/get-symbol-description-1.1.0.tgz#7bdd54e0befe8ffc9f3b4e203220d9f1e881b6ee"
@ -4868,6 +4919,11 @@ https-proxy-agent@^7.0.5, https-proxy-agent@^7.0.6:
agent-base "^7.1.2"
debug "4"
human-signals@^2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-2.1.0.tgz#dc91fcba42e4d06e4abaed33b3e7a3c02f514ea0"
integrity sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==
iconv-lite@0.6.3, iconv-lite@^0.6.3:
version "0.6.3"
resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.6.3.tgz#a52f80bf38da1952eb5c681790719871a1a72501"
@ -4940,11 +4996,6 @@ internal-slot@^1.1.0:
hasown "^2.0.2"
side-channel "^1.1.0"
interpret@^1.0.0:
version "1.4.0"
resolved "https://registry.yarnpkg.com/interpret/-/interpret-1.4.0.tgz#665ab8bc4da27a774a40584e812e3e0fa45b1a1e"
integrity sha512-agE4QfB2Lkp9uICn7BAqoscw4SZP9kTE2hxiFI3jBPmXJfdqiahTbUuKGsMoN2GtqL9AxhYioAcVvgsb1HvRbA==
ip-address@^9.0.5:
version "9.0.5"
resolved "https://registry.yarnpkg.com/ip-address/-/ip-address-9.0.5.tgz#117a960819b08780c3bd1f14ef3c1cc1d3f3ea5a"
@ -5163,10 +5214,10 @@ is-shared-array-buffer@^1.0.2, is-shared-array-buffer@^1.0.4:
dependencies:
call-bound "^1.0.3"
is-stream@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-1.1.0.tgz#12d4a3dd4e68e0b79ceb8dbc84173ae80d91ca44"
integrity sha512-uQPm8kcs47jx38atAcWTVxyltQYoPT68y9aWYdV6yWXSyW8mzSat0TL6CiWdZeCdF3KrAvpVtnHbTv4RN+rqdQ==
is-stream@^2.0.0:
version "2.0.1"
resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-2.0.1.tgz#fac1e3d53b97ad5a9d0ae9cef2389f5810a5c077"
integrity sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==
is-string@^1.0.7, is-string@^1.1.1:
version "1.1.1"
@ -5650,6 +5701,11 @@ merge-descriptors@^2.0.0:
resolved "https://registry.yarnpkg.com/merge-descriptors/-/merge-descriptors-2.0.0.tgz#ea922f660635a2249ee565e0449f951e6b603808"
integrity sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==
merge-stream@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/merge-stream/-/merge-stream-2.0.0.tgz#52823629a14dd00c9770fb6ad47dc6310f2c1f60"
integrity sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==
merge2@^1.3.0, merge2@^1.4.1:
version "1.4.1"
resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.4.1.tgz#4368892f885e907455a6fd7dc55c0c9d404990ae"
@ -5793,16 +5849,16 @@ ms@2.1.3, ms@^2.1.1, ms@^2.1.3:
resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2"
integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==
msw@2.7.6:
version "2.7.6"
resolved "https://registry.yarnpkg.com/msw/-/msw-2.7.6.tgz#1471ce4311f4c173f287dced31dee211b6958deb"
integrity sha512-P+rwn43ktxN8ghcl8q+hSAUlEi0PbJpDhGmDkw4zeUnRj3hBCVynWD+dTu38yLYKCE9ZF1OYcvpy7CTBRcqkZA==
msw@2.10.2:
version "2.10.2"
resolved "https://registry.yarnpkg.com/msw/-/msw-2.10.2.tgz#e7a56ed0b6865b00a30b4c4a5b59e5388fd48315"
integrity sha512-RCKM6IZseZQCWcSWlutdf590M8nVfRHG1ImwzOtwz8IYxgT4zhUO0rfTcTvDGiaFE0Rhcc+h43lcF3Jc9gFtwQ==
dependencies:
"@bundled-es-modules/cookie" "^2.0.1"
"@bundled-es-modules/statuses" "^1.0.1"
"@bundled-es-modules/tough-cookie" "^0.1.6"
"@inquirer/confirm" "^5.0.0"
"@mswjs/interceptors" "^0.37.0"
"@mswjs/interceptors" "^0.39.1"
"@open-draft/deferred-promise" "^2.2.0"
"@open-draft/until" "^2.1.0"
"@types/cookie" "^0.6.0"
@ -5822,7 +5878,7 @@ mute-stream@^2.0.0:
resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-2.0.0.tgz#a5446fc0c512b71c83c44d908d5c7b7b4c493b2b"
integrity sha512-WWdIxpyjEn+FhQJQQv9aQAYlHoNVdzIzUySNV1gHUPDSdZJ3yZn7pAAbQcV7B56Mvu881q9FZV+0Vx2xC44VWA==
nanoid@^3.3.8:
nanoid@^3.3.11, nanoid@^3.3.8:
version "3.3.11"
resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.11.tgz#4f4f112cefbe303202f2199838128936266d185b"
integrity sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==
@ -5842,11 +5898,6 @@ netmask@^2.0.2:
resolved "https://registry.yarnpkg.com/netmask/-/netmask-2.0.2.tgz#8b01a07644065d536383835823bc52004ebac5e7"
integrity sha512-dBpDMdxv9Irdq66304OLfEmQ9tbNRFnFTuZiLo+bD+r332bBmMJ8GBLXklIXXgxd3+v9+KUnZaUR5PJMa75Gsg==
nice-try@^1.0.4:
version "1.0.5"
resolved "https://registry.yarnpkg.com/nice-try/-/nice-try-1.0.5.tgz#a3378a7696ce7d223e88fc9b764bd7ef1089e366"
integrity sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==
nightwatch-axe-verbose@^2.3.0:
version "2.3.1"
resolved "https://registry.yarnpkg.com/nightwatch-axe-verbose/-/nightwatch-axe-verbose-2.3.1.tgz#42cd226989cb5205b699db42d74b1b967587d099"
@ -5921,12 +5972,12 @@ normalize-range@^0.1.2:
resolved "https://registry.yarnpkg.com/normalize-range/-/normalize-range-0.1.2.tgz#2d10c06bdfd312ea9777695a4d28439456b75942"
integrity sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==
npm-run-path@^2.0.0:
version "2.0.2"
resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-2.0.2.tgz#35a9232dfa35d7067b4cb2ddf2357b1871536c5f"
integrity sha512-lJxZYlT4DW/bRUtFh1MQIWqmLwQfAxnqWG4HhEdjMlkrJYnJn0Jrr2u3mgxqaWsdiBc76TYkTG/mhrnYTuzfHw==
npm-run-path@^4.0.1:
version "4.0.1"
resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-4.0.1.tgz#b7ecd1e5ed53da8e37a55e1c2269e0b97ed748ea"
integrity sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==
dependencies:
path-key "^2.0.0"
path-key "^3.0.0"
nth-check@^2.1.1:
version "2.1.1"
@ -6018,7 +6069,7 @@ once@^1.3.0, once@^1.3.1, once@^1.4.0:
dependencies:
wrappy "1"
onetime@^5.1.0:
onetime@^5.1.0, onetime@^5.1.2:
version "5.1.2"
resolved "https://registry.yarnpkg.com/onetime/-/onetime-5.1.2.tgz#d0e96ebb56b07476df1dd9c4806e5237985ca45e"
integrity sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==
@ -6075,11 +6126,6 @@ own-keys@^1.0.1:
object-keys "^1.1.1"
safe-push-apply "^1.0.0"
p-finally@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/p-finally/-/p-finally-1.0.0.tgz#3fbcfb15b899a44123b34b6dcc18b724336a2cae"
integrity sha512-LICb2p9CB7FS+0eR1oqWnHhp0FljGLZCWBE9aix0Uye9W8LTQPwMTYVGWQWIw9RdQiDg4+epXQODwIYJtSJaow==
p-limit@^2.0.0, p-limit@^2.2.0:
version "2.3.0"
resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-2.3.0.tgz#3dd33c647a214fdfffd835933eb086da0dc21db1"
@ -6203,12 +6249,7 @@ path-is-absolute@^1.0.0:
resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f"
integrity sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==
path-key@^2.0.0, path-key@^2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/path-key/-/path-key-2.0.1.tgz#411cadb574c5a140d3a4b1910d40d80cc9f40b40"
integrity sha512-fEHGKCSmUSDPv4uoj8AlD+joPlq3peND+HRYyxFz4KPw4z926S/b8rIuFs2FYJg3BwsxJf6A9/3eIdLaYC+9Dw==
path-key@^3.1.0:
path-key@^3.0.0, path-key@^3.1.0:
version "3.1.1"
resolved "https://registry.yarnpkg.com/path-key/-/path-key-3.1.1.tgz#581f6ade658cbba65a0d3380de7753295054f375"
integrity sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==
@ -6416,6 +6457,15 @@ postcss@8.5.3, postcss@^8.4.48, postcss@^8.5.0, postcss@^8.5.3:
picocolors "^1.1.1"
source-map-js "^1.2.1"
postcss@^8.5.6:
version "8.5.6"
resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.5.6.tgz#2825006615a619b4f62a9e7426cc120b349a8f3c"
integrity sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==
dependencies:
nanoid "^3.3.11"
picocolors "^1.1.1"
source-map-js "^1.2.1"
prelude-ls@^1.2.1:
version "1.2.1"
resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396"
@ -6596,13 +6646,6 @@ readdirp@~3.6.0:
dependencies:
picomatch "^2.2.1"
rechoir@^0.6.2:
version "0.6.2"
resolved "https://registry.yarnpkg.com/rechoir/-/rechoir-0.6.2.tgz#85204b54dba82d5742e28c96756ef43af50e3384"
integrity sha512-HFM8rkZ+i3zrV+4LQjwQ0W+ez98pApMGM3HUrN04j3CqzPOzl9nmP15Y8YXNm8QHGv/eacOVEjqhmWpkRV0NAw==
dependencies:
resolve "^1.1.6"
reflect.getprototypeof@^1.0.6, reflect.getprototypeof@^1.0.9:
version "1.0.10"
resolved "https://registry.yarnpkg.com/reflect.getprototypeof/-/reflect.getprototypeof-1.0.10.tgz#c629219e78a3316d8b604c765ef68996964e7bf9"
@ -6705,7 +6748,7 @@ resolve-pkg-maps@^1.0.0:
resolved "https://registry.yarnpkg.com/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz#616b3dc2c57056b5588c31cdf4b3d64db133720f"
integrity sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==
resolve@^1.1.6, resolve@^1.14.2, resolve@^1.22.4:
resolve@^1.14.2, resolve@^1.22.4:
version "1.22.10"
resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.10.tgz#b663e83ffb09bbf2386944736baae803029b8b39"
integrity sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==
@ -6832,10 +6875,10 @@ safe-regex-test@^1.1.0:
resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a"
integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==
sass@1.87.0:
version "1.87.0"
resolved "https://registry.yarnpkg.com/sass/-/sass-1.87.0.tgz#8cceb36fa63fb48a8d5d7f2f4c13b49c524b723e"
integrity sha512-d0NoFH4v6SjEK7BoX810Jsrhj7IQSYHAHLi/iSpgqKc7LaIDshFRlSg5LOymf9FqQhxEHs2W5ZQXlvy0KD45Uw==
sass@1.89.2:
version "1.89.2"
resolved "https://registry.yarnpkg.com/sass/-/sass-1.89.2.tgz#a771716aeae774e2b529f72c0ff2dfd46c9de10e"
integrity sha512-xCmtksBKd/jdJ9Bt9p7nPKiuqrlBMBuuGkQlkhZjjQk3Ty48lv93k5Dq6OPkKt4XwxDJ7tvlfrTa1MPA9bf+QA==
dependencies:
chokidar "^4.0.0"
immutable "^5.0.2"
@ -6872,12 +6915,12 @@ semver@7.5.4:
dependencies:
lru-cache "^6.0.0"
semver@7.7.1, semver@^7.3.5, semver@^7.5.3, semver@^7.5.4, semver@^7.6.3:
version "7.7.1"
resolved "https://registry.yarnpkg.com/semver/-/semver-7.7.1.tgz#abd5098d82b18c6c81f6074ff2647fd3e7220c9f"
integrity sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==
semver@7.7.2:
version "7.7.2"
resolved "https://registry.yarnpkg.com/semver/-/semver-7.7.2.tgz#67d99fdcd35cec21e6f8b87a7fd515a33f982b58"
integrity sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==
semver@^5.5.0, semver@^5.6.0:
semver@^5.6.0:
version "5.7.2"
resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.2.tgz#48d55db737c3287cd4835e17fa13feace1c41ef8"
integrity sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==
@ -6887,6 +6930,11 @@ semver@^6.3.1:
resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.1.tgz#556d2ef8689146e46dcea4bfdd095f3434dffcb4"
integrity sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==
semver@^7.3.5, semver@^7.5.3, semver@^7.5.4, semver@^7.6.3:
version "7.7.1"
resolved "https://registry.yarnpkg.com/semver/-/semver-7.7.1.tgz#abd5098d82b18c6c81f6074ff2647fd3e7220c9f"
integrity sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==
send@^1.1.0, send@^1.2.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/send/-/send-1.2.0.tgz#32a7554fb777b831dfa828370f773a3808d37212"
@ -6974,13 +7022,6 @@ shallow-clone@^3.0.0:
dependencies:
kind-of "^6.0.2"
shebang-command@^1.2.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-1.2.0.tgz#44aac65b695b03398968c39f363fee5deafdf1ea"
integrity sha512-EV3L1+UQWGor21OmnvojK36mhg+TyIKDh3iFBKBohr5xeXIhNBcx8oWdgkTEEQ+BEFFYdLRuqMfd5L84N1V5Vg==
dependencies:
shebang-regex "^1.0.0"
shebang-command@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-2.0.0.tgz#ccd0af4f8835fbdc265b82461aaf0c36663f34ea"
@ -6988,25 +7029,18 @@ shebang-command@^2.0.0:
dependencies:
shebang-regex "^3.0.0"
shebang-regex@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-1.0.0.tgz#da42f49740c0b42db2ca9728571cb190c98efea3"
integrity sha512-wpoSFAxys6b2a2wHZ1XpDSgD7N9iVjg29Ph9uV/uaP9Ex/KXlkTZTeddxDPSYQpgvzKLGJke2UU0AzoGCjNIvQ==
shebang-regex@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-3.0.0.tgz#ae16f1644d873ecad843b0307b143362d4c42172"
integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==
shelljs@0.9.2:
version "0.9.2"
resolved "https://registry.yarnpkg.com/shelljs/-/shelljs-0.9.2.tgz#a8ac724434520cd7ae24d52071e37a18ac2bb183"
integrity sha512-S3I64fEiKgTZzKCC46zT/Ib9meqofLrQVbpSswtjFfAVDW+AZ54WTnAM/3/yENoxz/V1Cy6u3kiiEbQ4DNphvw==
shelljs@0.10.0:
version "0.10.0"
resolved "https://registry.yarnpkg.com/shelljs/-/shelljs-0.10.0.tgz#e3bbae99b0f3f0cc5dce05b46a346fae2090e883"
integrity sha512-Jex+xw5Mg2qMZL3qnzXIfaxEtBaC4n7xifqaqtrZDdlheR70OGkydrPJWT0V1cA1k3nanC86x9FwAmQl6w3Klw==
dependencies:
execa "^1.0.0"
execa "^5.1.1"
fast-glob "^3.3.2"
interpret "^1.0.0"
rechoir "^0.6.2"
side-channel-list@^1.0.0:
version "1.0.0"
@ -7053,7 +7087,7 @@ siginfo@^2.0.0:
resolved "https://registry.yarnpkg.com/siginfo/-/siginfo-2.0.0.tgz#32e76c70b79724e3bb567cb9d543eb858ccfaf30"
integrity sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==
signal-exit@^3.0.0, signal-exit@^3.0.2:
signal-exit@^3.0.2, signal-exit@^3.0.3:
version "3.0.7"
resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.7.tgz#a9a1767f8af84155114eaabd73f99273c8f59ad9"
integrity sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==
@ -7293,10 +7327,10 @@ strip-bom@^3.0.0:
resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-3.0.0.tgz#2334c18e9c759f7bdd56fdef7e9ae3d588e68ed3"
integrity sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==
strip-eof@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/strip-eof/-/strip-eof-1.0.0.tgz#bb43ff5598a6eb05d89b59fcd129c983313606bf"
integrity sha512-7FCwGGmx8mD5xQd3RPUvnSpUXHM3BWuzjtpD4TXsfcZ9EL4azvVVUscFYwD9nx8Kh+uCBC00XBtAykoMHwTh8Q==
strip-final-newline@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/strip-final-newline/-/strip-final-newline-2.0.0.tgz#89b852fb2fcbe936f6f4b3187afb0a12c1ab58ad"
integrity sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==
strip-json-comments@3.1.1, strip-json-comments@^3.1.1:
version "3.1.1"
@ -7910,16 +7944,16 @@ vue-virtual-scroller@^2.0.0-beta.7:
vue-observe-visibility "^2.0.0-alpha.1"
vue-resize "^2.0.0-alpha.1"
vue@3.5.13:
version "3.5.13"
resolved "https://registry.yarnpkg.com/vue/-/vue-3.5.13.tgz#9f760a1a982b09c0c04a867903fc339c9f29ec0a"
integrity sha512-wmeiSMxkZCSc+PM2w2VRsOYAZC8GdipNFRTsLSfodVqI9mbejKeXEGr8SckuLnrQPGe3oJN5c3K0vpoU9q/wCQ==
vue@3.5.17:
version "3.5.17"
resolved "https://registry.yarnpkg.com/vue/-/vue-3.5.17.tgz#ea8a6a45abb2b0620e7d479319ce8434b55650cf"
integrity sha512-LbHV3xPN9BeljML+Xctq4lbz2lVHCR6DtbpTf5XIO6gugpXUN49j2QQPcMj086r9+AkJ0FfUT8xjulKKBkkr9g==
dependencies:
"@vue/compiler-dom" "3.5.13"
"@vue/compiler-sfc" "3.5.13"
"@vue/runtime-dom" "3.5.13"
"@vue/server-renderer" "3.5.13"
"@vue/shared" "3.5.13"
"@vue/compiler-dom" "3.5.17"
"@vue/compiler-sfc" "3.5.17"
"@vue/runtime-dom" "3.5.17"
"@vue/server-renderer" "3.5.17"
"@vue/shared" "3.5.17"
vuex@4.1.0:
version "4.1.0"
@ -8025,7 +8059,7 @@ which-typed-array@^1.1.13, which-typed-array@^1.1.16, which-typed-array@^1.1.18:
gopd "^1.2.0"
has-tostringtag "^1.0.2"
which@^1.2.9, which@^1.3.1:
which@^1.3.1:
version "1.3.1"
resolved "https://registry.yarnpkg.com/which/-/which-1.3.1.tgz#a45043d54f5805316da8d62f9f50918d3da70b0a"
integrity sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==